improved unicode support, now correctly parses up to utf-32
restricted boolean values to lowercase false/true
This commit is contained in:
parent
52def01e5f
commit
48b4745210
32 changed files with 590 additions and 358 deletions
6
pom.xml
6
pom.xml
|
|
@ -28,9 +28,9 @@
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-text</artifactId>
|
||||||
<version>2.6</version>
|
<version>1.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.*;
|
import java.nio.charset.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* storage like ArrayList, with specialized array
|
* storage like ArrayList, with bytebuffer instead of array
|
||||||
*/
|
*/
|
||||||
public class ByteBuf {
|
public class ByteBuf {
|
||||||
|
|
||||||
|
|
@ -19,19 +19,22 @@ public class ByteBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
return toString(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString(Charset charset) {
|
||||||
data.flip();
|
data.flip();
|
||||||
|
|
||||||
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); // decode is not threadsafe, might put it in threadlocal
|
CharsetDecoder decoder = charset.newDecoder(); // decode is not threadsafe, might put it in threadlocal
|
||||||
// but I don't think this (newDecoder()+config) is expensive
|
// but I don't think this (newDecoder()+config) is expensive
|
||||||
|
|
||||||
decoder.onMalformedInput(CodingErrorAction.REPLACE)
|
decoder.onMalformedInput(CodingErrorAction.REPLACE)
|
||||||
.onUnmappableCharacter(CodingErrorAction.REPLACE)
|
.onUnmappableCharacter(CodingErrorAction.REPLACE);
|
||||||
;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return decoder.decode(data).toString();
|
return decoder.decode(data).toString();
|
||||||
} catch (CharacterCodingException e) {
|
} catch (CharacterCodingException e) {
|
||||||
throw new JsonReadException(e);
|
throw new JsonParseException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,249 +0,0 @@
|
||||||
package nl.sander.jsontoy2;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class IoReader {
|
|
||||||
private static final int HEX_RADIX = 16;
|
|
||||||
private final InputStream inputStream;
|
|
||||||
private final ByteBuf characterBuffer = new ByteBuf();
|
|
||||||
private final ByteBuf encodedCodePointBuffer = new ByteBuf(4);
|
|
||||||
private final ByteBuffer convertBuffer = ByteBuffer.allocate(4);
|
|
||||||
private boolean escaping = false;
|
|
||||||
private boolean encoded = false;
|
|
||||||
private byte current;
|
|
||||||
private int linecount = 0;
|
|
||||||
|
|
||||||
protected IoReader(InputStream inputStream) {
|
|
||||||
this.inputStream = inputStream;
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected IoReader(String jsonString) {
|
|
||||||
this.inputStream = new ByteArrayInputStream(jsonString.getBytes());
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Integer readInteger() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Double.valueOf(value).intValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Long readLong() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Long.parseLong(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Float readFloat() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Float.parseFloat(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Double readDouble() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Double.parseDouble(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Short readShort() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Short.parseShort(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Byte readByte() {
|
|
||||||
String value = readNumeric();
|
|
||||||
return Byte.parseByte(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Character readCharacter() {
|
|
||||||
eatUntil('\"');
|
|
||||||
char currentChar = (char) current;
|
|
||||||
eatUntil('\"');
|
|
||||||
return currentChar;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean readBoolean() {
|
|
||||||
characterBuffer.clear();
|
|
||||||
while (Character.isAlphabetic(current)) {
|
|
||||||
characterBuffer.add(current);
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return characterBuffer.toString().equalsIgnoreCase("TRUE");
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isNumeric(int c) {
|
|
||||||
return Character.isDigit(c) || c == '.' || c == 'e' || c == '-' || c == '+';
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readNumeric() {
|
|
||||||
characterBuffer.clear();
|
|
||||||
if (current == '-') {
|
|
||||||
characterBuffer.add(current);
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
while (current > -1 && isNumeric(current)) {
|
|
||||||
characterBuffer.add(current);
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
return characterBuffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<?> readList() {
|
|
||||||
skipWhitespace();
|
|
||||||
if (current != '[') {
|
|
||||||
throw new JsonReadException("no list found");
|
|
||||||
}
|
|
||||||
List<Object> list = new ArrayList<>();
|
|
||||||
advance();
|
|
||||||
while (current != -1 && current != ']') {
|
|
||||||
Optional<Object> maybeValue = readValue();
|
|
||||||
if (maybeValue.isEmpty()) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
list.add(maybeValue.get());
|
|
||||||
eatUntil(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<?, ?> readMap() {
|
|
||||||
HashMap<Object, Object> map = new HashMap<>();
|
|
||||||
skipWhitespace();
|
|
||||||
if (current != '{') {
|
|
||||||
throw new JsonReadException("no map found");
|
|
||||||
}
|
|
||||||
while (current != -1 && current != '}') {
|
|
||||||
skipWhitespace();
|
|
||||||
if (current == '"') {
|
|
||||||
String key = readString();
|
|
||||||
eatUntil(':');
|
|
||||||
skipWhitespace();
|
|
||||||
Optional<Object> maybeValue = readValue();
|
|
||||||
maybeValue.ifPresent(o -> map.put(key, o));
|
|
||||||
eatUntil(',');
|
|
||||||
} else {
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<Object> readValue() {
|
|
||||||
Object value;
|
|
||||||
skipWhitespace();
|
|
||||||
if (current == ']' || current == '}') {
|
|
||||||
return Optional.empty();
|
|
||||||
} else if (current == '[') {
|
|
||||||
value = readList();
|
|
||||||
} else if (current == '{') {
|
|
||||||
value = readMap();
|
|
||||||
} else if (current == '\"') {
|
|
||||||
value = readString();
|
|
||||||
} else if (current == 'T' || current == 't' || current == 'F' || current == 'f') {
|
|
||||||
value = readBoolean();
|
|
||||||
} else {
|
|
||||||
String numeric = readNumeric();
|
|
||||||
double doubleValue = Double.parseDouble(numeric);
|
|
||||||
if ((int) doubleValue == doubleValue) {
|
|
||||||
value = (int) doubleValue;
|
|
||||||
} else {
|
|
||||||
value = doubleValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Optional.of(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String readString() {
|
|
||||||
eatUntil('\"');
|
|
||||||
|
|
||||||
characterBuffer.clear();
|
|
||||||
boolean endOfString = false;
|
|
||||||
while (current > -1 && !endOfString) {
|
|
||||||
if (current == '\\' && !escaping) {
|
|
||||||
escaping = true;
|
|
||||||
} else {
|
|
||||||
if (!escaping) {
|
|
||||||
// regular character
|
|
||||||
if (current != '\"') {
|
|
||||||
characterBuffer.add(current);
|
|
||||||
} else {
|
|
||||||
endOfString = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// unicode codepoint
|
|
||||||
if (current == 'u') {
|
|
||||||
encoded = true;
|
|
||||||
} else if (current == 'n') {
|
|
||||||
linecount++;
|
|
||||||
characterBuffer.add("\n".getBytes());
|
|
||||||
escaping = false;
|
|
||||||
} else if (current == '\"') {
|
|
||||||
characterBuffer.add("\"".getBytes());
|
|
||||||
escaping = false;
|
|
||||||
} else if (current == '\'') {
|
|
||||||
throw new JsonReadException("illegal escaped quote in line " + linecount);
|
|
||||||
} else {
|
|
||||||
if (encoded) {
|
|
||||||
// load next 4 characters in special buffer to convert to int
|
|
||||||
encodedCodePointBuffer.add(current);
|
|
||||||
if (encodedCodePointBuffer.length() == 4) {
|
|
||||||
byte[] bytes = parseCodePoint();
|
|
||||||
characterBuffer.add(bytes);
|
|
||||||
encoded = false;
|
|
||||||
escaping = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
return characterBuffer.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] parseCodePoint() {
|
|
||||||
String hex = encodedCodePointBuffer.toString();
|
|
||||||
int codepoint = Integer.parseInt(hex, HEX_RADIX);
|
|
||||||
convertBuffer.clear();
|
|
||||||
encodedCodePointBuffer.clear();
|
|
||||||
return convertBuffer.putInt(codepoint).array();
|
|
||||||
}
|
|
||||||
|
|
||||||
void advance() {
|
|
||||||
try {
|
|
||||||
current = (byte) inputStream.read();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void skipWhitespace() {
|
|
||||||
try {
|
|
||||||
while (current > -1 && Character.isWhitespace(current)) {
|
|
||||||
current = (byte) inputStream.read();
|
|
||||||
}
|
|
||||||
if (current == -1) {
|
|
||||||
throw new JsonReadException("end of source reached");
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String eatUntil(char until) {
|
|
||||||
characterBuffer.clear();
|
|
||||||
|
|
||||||
while (current > -1 && (current != until | Character.isWhitespace(current))) {
|
|
||||||
characterBuffer.add(current);
|
|
||||||
advance();
|
|
||||||
}
|
|
||||||
advance();
|
|
||||||
return characterBuffer.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/main/java/nl/sander/jsontoy2/JsonParseException.java
Normal file
12
src/main/java/nl/sander/jsontoy2/JsonParseException.java
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
public class JsonParseException extends RuntimeException {
|
||||||
|
|
||||||
|
public JsonParseException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonParseException(Throwable e) {
|
||||||
|
super(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package nl.sander.jsontoy2;
|
|
||||||
|
|
||||||
public class JsonReadException extends RuntimeException {
|
|
||||||
|
|
||||||
public JsonReadException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public JsonReadException(Throwable e) {
|
|
||||||
super(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -16,17 +16,42 @@ import java.util.concurrent.ConcurrentMap;
|
||||||
public class JsonReader {
|
public class JsonReader {
|
||||||
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
|
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* reads a value for a type that is not known beforehand
|
||||||
|
*
|
||||||
|
* @param stream the underlying stream to read
|
||||||
|
* @return an Object with runtime type as follows:
|
||||||
|
* "null" => null
|
||||||
|
* "true"/"false" => Boolean
|
||||||
|
* integral number => Integer //TODO Long?
|
||||||
|
* floating point number => Double
|
||||||
|
* string => String
|
||||||
|
* object => HashMap
|
||||||
|
* array => List
|
||||||
|
*/
|
||||||
|
public static Object read(InputStream stream) {
|
||||||
|
return read(new Parser(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object read(String jsonString) {
|
||||||
|
return read(new Parser(jsonString));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Object read(Parser parser) {
|
||||||
|
return parser.parseAny();
|
||||||
|
}
|
||||||
|
|
||||||
public static <T> T read(Class<T> type, InputStream reader) {
|
public static <T> T read(Class<T> type, InputStream reader) {
|
||||||
return read(type, new IoReader(reader));
|
return read(type, new Parser(reader));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T read(Class<T> type, String jsonString) {
|
public static <T> T read(Class<T> type, String jsonString) {
|
||||||
return read(type, new IoReader(jsonString));
|
return read(type, new Parser(jsonString));
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static <T> T read(Class<T> type, IoReader ioReader) {
|
static <T> T read(Class<T> type, Parser parser) {
|
||||||
return (T) getReader(type).read(ioReader);
|
return (T) getReader(type).read(parser);
|
||||||
// class.cast() does not work for primitives;
|
// class.cast() does not work for primitives;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package nl.sander.jsontoy2;
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
import java.io.Reader;
|
|
||||||
|
|
||||||
public interface JsonValueReader<T> {
|
public interface JsonValueReader<T> {
|
||||||
T read(IoReader ioReader);
|
T read(Parser parser);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
58
src/main/java/nl/sander/jsontoy2/Lexer.java
Normal file
58
src/main/java/nl/sander/jsontoy2/Lexer.java
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* implements lowest level operations on inputstream.
|
||||||
|
*/
|
||||||
|
public class Lexer {
|
||||||
|
protected final InputStream inputStream;
|
||||||
|
protected final ByteBuf characterBuffer = new ByteBuf();
|
||||||
|
protected byte current;
|
||||||
|
|
||||||
|
public Lexer(InputStream inputStream) {
|
||||||
|
this.inputStream = inputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
void advance() {
|
||||||
|
try {
|
||||||
|
current = (byte) inputStream.read();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void skipWhitespace() {
|
||||||
|
try {
|
||||||
|
while (current > -1 && Character.isWhitespace(current)) {
|
||||||
|
current = (byte) inputStream.read();
|
||||||
|
}
|
||||||
|
if (current == -1) {
|
||||||
|
throw new JsonParseException("end of source reached");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void eatUntil(char until) {
|
||||||
|
while (current > -1 && (current != until | Character.isWhitespace(current))) {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
void expect(Supplier<JsonParseException> exceptionSupplier, char... word) {
|
||||||
|
int increment = 0;
|
||||||
|
|
||||||
|
while (current > -1 && increment < word.length) {
|
||||||
|
if (current != word[increment++]) {
|
||||||
|
throw exceptionSupplier.get();
|
||||||
|
} else {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/java/nl/sander/jsontoy2/Maybe.java
Normal file
40
src/main/java/nl/sander/jsontoy2/Maybe.java
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Option that may contain null
|
||||||
|
*/
|
||||||
|
public class Maybe<T> {
|
||||||
|
private final boolean hasValue;
|
||||||
|
private final T value;
|
||||||
|
|
||||||
|
private Maybe(boolean hasValue, T value) {
|
||||||
|
this.hasValue = hasValue;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Maybe<T> none() {
|
||||||
|
return new Maybe<>(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Maybe<T> of(T value) {
|
||||||
|
return new Maybe<>(true, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPresent() {
|
||||||
|
return hasValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ifPresent(Consumer<T> action) {
|
||||||
|
if (hasValue) {
|
||||||
|
action.accept(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
278
src/main/java/nl/sander/jsontoy2/Parser.java
Normal file
278
src/main/java/nl/sander/jsontoy2/Parser.java
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the input as a stream.
|
||||||
|
* Implements parsing the lowlevel types (primitives, list, map).
|
||||||
|
* <p>
|
||||||
|
* Not threadsafe, not meant to be used directly
|
||||||
|
*/
|
||||||
|
public class Parser extends Lexer {
|
||||||
|
|
||||||
|
private static final int HEX_RADIX = 16;
|
||||||
|
private final ByteBuf encodedCodePointBuffer = new ByteBuf(4);
|
||||||
|
private boolean escaping = false;
|
||||||
|
private boolean encoded = false;
|
||||||
|
private int linecount = 0;
|
||||||
|
|
||||||
|
protected Parser(InputStream inputStream) {
|
||||||
|
super(inputStream);
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Parser(String jsonString) {
|
||||||
|
super(new ByteArrayInputStream(jsonString.getBytes()));
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer parseInteger() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Double.valueOf(value).intValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long parseLong() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Long.parseLong(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Float parseFloat() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Float.parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double parseDouble() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Double.parseDouble(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Short parseShort() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Short.parseShort(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Byte parseByte() {
|
||||||
|
String value = parseNumber();
|
||||||
|
return Byte.parseByte(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Character parseCharacter() {
|
||||||
|
String string = parseString();
|
||||||
|
return string.charAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean parseBoolean() {
|
||||||
|
characterBuffer.clear();
|
||||||
|
while (Character.isAlphabetic(current)) {
|
||||||
|
characterBuffer.add(current);
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
String maybeBoolean = characterBuffer.toString();
|
||||||
|
boolean returnValue;
|
||||||
|
if ((returnValue = maybeBoolean.equals("true")) || maybeBoolean.equals("false")) {
|
||||||
|
return returnValue;
|
||||||
|
} else {
|
||||||
|
throw new JsonParseException("Illegal boolean value: " + maybeBoolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPartOfNumber(int c) {
|
||||||
|
return Character.isDigit(c) || c == '.' || c == 'E' || c == 'e' || c == '-' || c == '+';
|
||||||
|
}
|
||||||
|
|
||||||
|
private String parseNumber() {
|
||||||
|
characterBuffer.clear();
|
||||||
|
if (current == '-') {
|
||||||
|
characterBuffer.add(current);
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
while (current > -1 && isPartOfNumber(current)) {
|
||||||
|
characterBuffer.add(current);
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
return characterBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<?> parseArray() {
|
||||||
|
skipWhitespace();
|
||||||
|
if (current != '[') {
|
||||||
|
throw new JsonParseException("no list found");
|
||||||
|
}
|
||||||
|
List<Object> list = new ArrayList<>();
|
||||||
|
advance();
|
||||||
|
while (current != -1 && current != ']') {
|
||||||
|
Maybe<Object> maybeValue = parseValue();
|
||||||
|
if (!maybeValue.isPresent()) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
list.add(maybeValue.get());
|
||||||
|
eatUntil(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<?, ?> parseObject() {
|
||||||
|
HashMap<Object, Object> map = new HashMap<>();
|
||||||
|
skipWhitespace();
|
||||||
|
if (current != '{') {
|
||||||
|
throw new JsonParseException("no map found");
|
||||||
|
}
|
||||||
|
while (current != -1 && current != '}') {
|
||||||
|
skipWhitespace();
|
||||||
|
if (current == '"') {
|
||||||
|
String key = parseString();
|
||||||
|
eatUntil(':');
|
||||||
|
skipWhitespace();
|
||||||
|
Maybe<Object> maybeValue = parseValue();
|
||||||
|
maybeValue.ifPresent(o -> map.put(key, o));
|
||||||
|
eatUntil(',');
|
||||||
|
} else {
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object parseAny() {
|
||||||
|
Maybe<Object> maybe = parseValue();
|
||||||
|
if (maybe.isPresent()) {
|
||||||
|
return maybe.get();
|
||||||
|
} else {
|
||||||
|
throw new JsonParseException("no value found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Maybe<Object> parseValue() {
|
||||||
|
Object value;
|
||||||
|
skipWhitespace();
|
||||||
|
switch (current) {
|
||||||
|
case ']':
|
||||||
|
case '}': return Maybe.none();
|
||||||
|
case '[': value = parseArray();
|
||||||
|
break;
|
||||||
|
case '{': value = parseObject();
|
||||||
|
break;
|
||||||
|
case '\"': value = parseString();
|
||||||
|
break;
|
||||||
|
case 'T':
|
||||||
|
case 't':
|
||||||
|
case 'F':
|
||||||
|
case 'f': value = parseBoolean();
|
||||||
|
break;
|
||||||
|
case 'n': value = readNull();
|
||||||
|
break;
|
||||||
|
default: String numeric = parseNumber();
|
||||||
|
double doubleValue = Double.parseDouble(numeric);
|
||||||
|
if ((int) doubleValue == doubleValue) {
|
||||||
|
value = (int) doubleValue;
|
||||||
|
} else {
|
||||||
|
value = doubleValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Maybe.of(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Object readNull() {
|
||||||
|
expect(() -> new JsonParseException("Expected 'null', encountered " + (char) current), 'n', 'u', 'l', 'l');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String parseString() {
|
||||||
|
eatUntil('\"');
|
||||||
|
|
||||||
|
characterBuffer.clear();
|
||||||
|
boolean endOfString = false;
|
||||||
|
while (current > -1 && !endOfString) {
|
||||||
|
if (current == '\\' && !escaping) {
|
||||||
|
escaping = true;
|
||||||
|
} else {
|
||||||
|
if (escaping) {
|
||||||
|
parseEscapedSequence();
|
||||||
|
} else {
|
||||||
|
endOfString = addOrEndOfString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
|
||||||
|
return characterBuffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseEscapedSequence() {
|
||||||
|
// unicode codepoint
|
||||||
|
if (encoded) {
|
||||||
|
parseEncoded();
|
||||||
|
} else {
|
||||||
|
parseNonEncodedEscapes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseNonEncodedEscapes() {
|
||||||
|
switch (current) {
|
||||||
|
case '\\': characterBuffer.add("\\".getBytes()); // backslash
|
||||||
|
break;
|
||||||
|
case '/': characterBuffer.add("/".getBytes()); // / the infamous escaped slash
|
||||||
|
break;
|
||||||
|
case 'b': characterBuffer.add((byte) 8); // backspace
|
||||||
|
break;
|
||||||
|
case 'f': characterBuffer.add((byte) 12); // formfeed
|
||||||
|
break;
|
||||||
|
case 't': characterBuffer.add((byte) 9); // tab
|
||||||
|
break;
|
||||||
|
case 'u': encoded = true; // \\ u hex hex hex hex encoded utf16/32
|
||||||
|
break;
|
||||||
|
case 'n': linecount++; // newline
|
||||||
|
characterBuffer.add("\n".getBytes());
|
||||||
|
escaping = false;
|
||||||
|
break;
|
||||||
|
case '\"': characterBuffer.add("\"".getBytes()); // quote
|
||||||
|
escaping = false;
|
||||||
|
break;
|
||||||
|
case '\'': throw new JsonParseException("illegal escaped quote in line " + linecount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseEncoded() {
|
||||||
|
StringBuilder buf = new StringBuilder();
|
||||||
|
char codePoint = parseCodePoint();
|
||||||
|
buf.append(codePoint);
|
||||||
|
if (Character.isHighSurrogate(codePoint)) {
|
||||||
|
expect(() -> new JsonParseException("Invalid unicode codepoint at line " + linecount), '\\', 'u');
|
||||||
|
char lowSurrogate = parseCodePoint();
|
||||||
|
if (Character.isLowSurrogate(lowSurrogate)) {
|
||||||
|
buf.append(lowSurrogate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
characterBuffer.add(buf.toString().getBytes());
|
||||||
|
encoded = false;
|
||||||
|
escaping = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// load next 4 characters in special buffer to convert to int
|
||||||
|
private char parseCodePoint() {
|
||||||
|
encodedCodePointBuffer.clear();
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
encodedCodePointBuffer.add(current);
|
||||||
|
advance();
|
||||||
|
}
|
||||||
|
return (char) Integer.parseInt(encodedCodePointBuffer.toString(), HEX_RADIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean addOrEndOfString() {
|
||||||
|
// regular character
|
||||||
|
if (current != '\"') {
|
||||||
|
characterBuffer.add(current);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
import java.time.temporal.ChronoField;
|
import java.time.temporal.ChronoField;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
public abstract class AbstractDatesReader<T> {
|
public abstract class AbstractDatesReader<T> {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class BooleanReader implements JsonValueReader<Boolean> {
|
public class BooleanReader implements JsonValueReader<Boolean> {
|
||||||
@Override
|
@Override
|
||||||
public Boolean read(IoReader ioReader) {
|
public Boolean read(Parser parser) {
|
||||||
return ioReader.readBoolean();
|
return parser.parseBoolean();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class ByteReader implements JsonValueReader<Byte> {
|
public class ByteReader implements JsonValueReader<Byte> {
|
||||||
@Override
|
@Override
|
||||||
public Byte read(IoReader ioReader) {
|
public Byte read(Parser parser) {
|
||||||
return ioReader.readByte();
|
return parser.parseByte();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class CharReader implements JsonValueReader<Character> {
|
public class CharReader implements JsonValueReader<Character> {
|
||||||
@Override
|
@Override
|
||||||
public Character read(IoReader ioReader) {
|
public Character read(Parser parser) {
|
||||||
return ioReader.readCharacter();
|
return parser.parseCharacter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -9,8 +9,8 @@ import java.util.Date;
|
||||||
public class DateReader extends AbstractDatesReader<Date> implements JsonValueReader<Date> {
|
public class DateReader extends AbstractDatesReader<Date> implements JsonValueReader<Date> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Date read(IoReader ioReader) {
|
public Date read(Parser parser) {
|
||||||
ZonedDateTime zdt = getZonedDateTime(ioReader::readString);
|
ZonedDateTime zdt = getZonedDateTime(parser::parseString);
|
||||||
return Date.from(zdt.toInstant());
|
return Date.from(zdt.toInstant());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class DoubleReader implements JsonValueReader<Double> {
|
public class DoubleReader implements JsonValueReader<Double> {
|
||||||
@Override
|
@Override
|
||||||
public Double read(IoReader ioReader) {
|
public Double read(Parser parser) {
|
||||||
return ioReader.readDouble();
|
return parser.parseDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class FloatReader implements JsonValueReader<Float> {
|
public class FloatReader implements JsonValueReader<Float> {
|
||||||
@Override
|
@Override
|
||||||
public Float read(IoReader ioReader) {
|
public Float read(Parser parser) {
|
||||||
return ioReader.readFloat();
|
return parser.parseFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class IntegerReader implements JsonValueReader<Integer> {
|
public class IntegerReader implements JsonValueReader<Integer> {
|
||||||
@Override
|
@Override
|
||||||
public Integer read(IoReader ioReader) {
|
public Integer read(Parser parser) {
|
||||||
return ioReader.readInteger();
|
return parser.parseInteger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public class ListReader implements JsonValueReader<List> {
|
public class ListReader implements JsonValueReader<List> {
|
||||||
@Override
|
@Override
|
||||||
public List<?> read(IoReader ioReader) {
|
public List<?> read(Parser parser) {
|
||||||
return ioReader.readList();
|
return parser.parseArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
|
|
@ -9,8 +9,8 @@ import java.time.ZonedDateTime;
|
||||||
public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> implements JsonValueReader<LocalDateTime> {
|
public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> implements JsonValueReader<LocalDateTime> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalDateTime read(IoReader ioReader) {
|
public LocalDateTime read(Parser parser) {
|
||||||
ZonedDateTime zdt = getZonedDateTime(ioReader::readString);
|
ZonedDateTime zdt = getZonedDateTime(parser::parseString);
|
||||||
return LocalDateTime.from(zdt);
|
return LocalDateTime.from(zdt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class LongReader implements JsonValueReader<Long> {
|
public class LongReader implements JsonValueReader<Long> {
|
||||||
@Override
|
@Override
|
||||||
public Long read(IoReader ioReader) {
|
public Long read(Parser parser) {
|
||||||
return ioReader.readLong();
|
return parser.parseLong();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public class MapReader implements JsonValueReader<Map> {
|
public class MapReader implements JsonValueReader<Map> {
|
||||||
@Override
|
@Override
|
||||||
public Map read(IoReader ioReader) {
|
public Map read(Parser parser) {
|
||||||
return ioReader.readMap();
|
return parser.parseObject();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class ShortReader implements JsonValueReader<Short> {
|
public class ShortReader implements JsonValueReader<Short> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Short read(IoReader ioReader) {
|
public Short read(Parser parser) {
|
||||||
return ioReader.readShort();
|
return parser.parseShort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
package nl.sander.jsontoy2.readers;
|
package nl.sander.jsontoy2.readers;
|
||||||
|
|
||||||
import nl.sander.jsontoy2.IoReader;
|
|
||||||
import nl.sander.jsontoy2.JsonValueReader;
|
import nl.sander.jsontoy2.JsonValueReader;
|
||||||
|
import nl.sander.jsontoy2.Parser;
|
||||||
|
|
||||||
public class StringReader implements JsonValueReader<String> {
|
public class StringReader implements JsonValueReader<String> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String read(IoReader ioReader) {
|
public String read(Parser parser) {
|
||||||
return ioReader.readString();
|
return parser.parseString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@ package nl.sander.jsontoy2;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
|
|
||||||
public class Booleans {
|
public class Booleans {
|
||||||
|
|
||||||
|
|
@ -12,6 +11,16 @@ public class Booleans {
|
||||||
assertEquals(true, JsonReader.read(Boolean.class, "true"));
|
assertEquals(true, JsonReader.read(Boolean.class, "true"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIllegalTrue() {
|
||||||
|
assertThrows(JsonParseException.class, () -> JsonReader.read(Boolean.class, "TRUE"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIllegalFalse() {
|
||||||
|
assertThrows(JsonParseException.class, () -> JsonReader.read(Boolean.class, "False"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFalse() {
|
public void testFalse() {
|
||||||
assertEquals(false, JsonReader.read(Boolean.class, "false"));
|
assertEquals(false, JsonReader.read(Boolean.class, "false"));
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,49 @@ public class Chars {
|
||||||
assertEquals('A', JsonReader.read(char.class, "\"AB\""));
|
assertEquals('A', JsonReader.read(char.class, "\"AB\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void tab() {
|
||||||
|
assertEquals('\t', JsonReader.read(char.class, "\"\\t\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void backspace() {
|
||||||
|
assertEquals('\b', JsonReader.read(char.class, "\"\\b\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void formfeed() {
|
||||||
|
assertEquals('\f', JsonReader.read(char.class, "\"\\f\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void backslash() {
|
||||||
|
assertEquals('\\', JsonReader.read(char.class, "\"\\\\\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void slash() {
|
||||||
|
assertEquals('/', JsonReader.read(char.class, "\"\\/\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newline() {
|
||||||
|
assertEquals('\n', JsonReader.read(char.class, "\"\\n\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unicode() {
|
||||||
|
assertEquals('\u0100', JsonReader.read(char.class, "\"\\u0100\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unicodeascii() {
|
||||||
|
assertEquals('A', JsonReader.read(char.class, "\"\\u0041\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testunicode32() {
|
||||||
|
// int codepoint = Character.codePointOf("\\uD834\\uDD1E");
|
||||||
|
assertEquals("\uD834\uDD1E", JsonReader.read("\"\\uD834\\uDD1E\""));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
src/test/java/nl/sander/jsontoy2/CommonsTextTest.java
Normal file
14
src/test/java/nl/sander/jsontoy2/CommonsTextTest.java
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class CommonsTextTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testunicode() {
|
||||||
|
assertEquals("\uD834\uDD1E", StringEscapeUtils.unescapeJson("\\uD834\\uDD1E"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,9 @@ package nl.sander.jsontoy2;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
@ -69,4 +71,11 @@ public class Lists {
|
||||||
List<?> expected = List.of(List.of(), Map.of("list", List.of()));
|
List<?> expected = List.of(List.of(), Map.of("list", List.of()));
|
||||||
assertEquals(expected, list);
|
assertEquals(expected, list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nullInList() {
|
||||||
|
List<Object> list = JsonReader.read(List.class, "[null]");
|
||||||
|
List<?> expected = Collections.singletonList(null);
|
||||||
|
assertEquals(expected, list);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,10 @@ package nl.sander.jsontoy2;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
@ -33,7 +36,7 @@ public class Maps {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void multipleStrings_noColon_error() {
|
public void multipleStrings_noColon_error() {
|
||||||
assertThrows(JsonReadException.class, () -> JsonReader.read(Map.class, " {\"hello\" \"jason\"}"));
|
assertThrows(JsonParseException.class, () -> JsonReader.read(Map.class, " {\"hello\" \"jason\"}"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -44,7 +47,7 @@ public class Maps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("raw")
|
@SuppressWarnings("rawtypes")
|
||||||
public void nestedMap() {
|
public void nestedMap() {
|
||||||
Map<String, Map> list = JsonReader.read(Map.class, "{\"map\": {\"map\":{}}}");
|
Map<String, Map> list = JsonReader.read(Map.class, "{\"map\": {\"map\":{}}}");
|
||||||
Map<String, Map> expected = new HashMap<>();
|
Map<String, Map> expected = new HashMap<>();
|
||||||
|
|
@ -56,6 +59,7 @@ public class Maps {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public void listInMap() {
|
public void listInMap() {
|
||||||
Map<String, List> list = JsonReader.read(Map.class, " { \"list\" : [ 1 ] } ");
|
Map<String, List> list = JsonReader.read(Map.class, " { \"list\" : [ 1 ] } ");
|
||||||
Map<String, List> expected = new HashMap<>();
|
Map<String, List> expected = new HashMap<>();
|
||||||
|
|
|
||||||
13
src/test/java/nl/sander/jsontoy2/Null.java
Normal file
13
src/test/java/nl/sander/jsontoy2/Null.java
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
package nl.sander.jsontoy2;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
|
|
||||||
|
public class Null {
|
||||||
|
@Test
|
||||||
|
public void readNull() {
|
||||||
|
assertNull(JsonReader.read("null"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,6 @@ package nl.sander.jsontoy2;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.nio.charset.CharacterCodingException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
|
||||||
|
|
@ -15,24 +13,19 @@ public class Strings {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void firstSurrogateButSecondMissing() throws NoSuchFieldException, IllegalAccessException {
|
public void firstSurrogateButSecondMissing() {
|
||||||
String value = JsonReader.read(String.class, "\"\\uDADA\"");
|
assertThrows(JsonParseException.class, () -> JsonReader.read(String.class, "\"\\uDADA\""));
|
||||||
|
|
||||||
assertEquals("\u0000\u0000<EFBFBD><EFBFBD>", value); // question mark
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void incompleteSurrogateAndEscapeValid() {
|
public void incompleteSurrogateAndEscapeValid() {
|
||||||
String value = JsonReader.read(String.class, " \"\\uD800\n\"");
|
assertThrows(JsonParseException.class, () -> JsonReader.read(String.class, " \"\\uD800\n\""));
|
||||||
assertEquals("\u0000\u0000<EFBFBD>\u0000\n", value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void firstValidSurrogateSecondInvalid() throws CharacterCodingException {
|
public void firstValidSurrogateSecondInvalid() {
|
||||||
String value = JsonReader.read(String.class, "\"\\uD888\\u1334\"");
|
String value = JsonReader.read(String.class, "\"\\uD888\\u1334\"");
|
||||||
|
assertEquals("?", value);
|
||||||
assertEquals("\u0000\u0000؈\u0000\u0000\u00134", value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -43,7 +36,7 @@ public class Strings {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void escapedSingleQuote() {
|
public void escapedSingleQuote() {
|
||||||
assertThrows(JsonReadException.class, () -> JsonReader.read(String.class, "\"\\'\""));
|
assertThrows(JsonParseException.class, () -> JsonReader.read(String.class, "\"\\'\""));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.nio.charset.CharacterCodingException;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
public class StringsWithJackson {
|
public class StringsWithJackson {
|
||||||
|
|
@ -19,15 +17,13 @@ public class StringsWithJackson {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void firstSurrogateButSecondMissing() throws NoSuchFieldException, IllegalAccessException, JsonProcessingException {
|
public void firstSurrogateButSecondMissing() throws JsonProcessingException {
|
||||||
String value = jackson.readValue("\"\\uDADA\"", String.class);
|
String value = jackson.readValue("\"\\uDADA\"", String.class);
|
||||||
|
|
||||||
assertTrue(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void incompleteSurrogateAndEscapeValid() throws JsonProcessingException {
|
public void incompleteSurrogateAndEscapeValid() {
|
||||||
assertThrows(JsonParseException.class, () -> jackson.readValue("\"\\uD800\n\"", String.class));
|
assertThrows(JsonParseException.class, () -> jackson.readValue("\"\\uD800\n\"", String.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue