added support for maps

This commit is contained in:
Sander Hautvast 2020-07-08 18:21:26 +02:00
parent b72935960f
commit 52def01e5f
21 changed files with 298 additions and 90 deletions

87
pom.xml Normal file
View file

@ -0,0 +1,87 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>JsonToy2</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<groupId>nl.sander</groupId>
<artifactId>jsontoy2</artifactId>
<version>0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.26.0-GA</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>1.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>1.10.19</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -16,7 +16,6 @@ public class IoReader {
private boolean encoded = false;
private byte current;
private int linecount = 0;
private long charcount = 0;
protected IoReader(InputStream inputStream) {
this.inputStream = inputStream;
@ -93,11 +92,11 @@ public class IoReader {
}
public List<?> readList() {
List<Object> list = new ArrayList<>();
skipWhitespace();
if (current != '[') {
throw new JsonReadException("no list found");
}
List<Object> list = new ArrayList<>();
advance();
while (current != -1 && current != ']') {
Optional<Object> maybeValue = readValue();
@ -105,17 +104,39 @@ public class IoReader {
break;
} else {
list.add(maybeValue.get());
eatUntilAny(',');
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 == ']') {
if (current == ']' || current == '}') {
return Optional.empty();
} else if (current == '[') {
value = readList();
@ -137,10 +158,6 @@ public class IoReader {
return Optional.of(value);
}
private Map<?, ?> readMap() {
return new HashMap<>();
}
public String readString() {
eatUntil('\"');
@ -158,7 +175,7 @@ public class IoReader {
endOfString = true;
}
} else {
// json encoded string
// unicode codepoint
if (current == 'u') {
encoded = true;
} else if (current == 'n') {
@ -172,7 +189,7 @@ public class IoReader {
throw new JsonReadException("illegal escaped quote in line " + linecount);
} else {
if (encoded) {
// load next 4 characters in special buffer
// load next 4 characters in special buffer to convert to int
encodedCodePointBuffer.add(current);
if (encodedCodePointBuffer.length() == 4) {
byte[] bytes = parseCodePoint();
@ -201,7 +218,6 @@ public class IoReader {
void advance() {
try {
current = (byte) inputStream.read();
System.out.println((charcount++) + ":" + (char) current);
} catch (IOException e) {
throw new IllegalStateException(e);
}
@ -230,24 +246,4 @@ public class IoReader {
advance();
return characterBuffer.toString();
}
String eatUntilAny(char... untilOrrChars) {
characterBuffer.clear();
while (current > -1 && (!contains(untilOrrChars, current) | Character.isWhitespace(current))) {
characterBuffer.add(current);
advance();
}
advance();
return characterBuffer.toString();
}
private boolean contains(char[] chars, byte search) {
for (char aChar : chars) {
if (search == aChar) {
return true;
}
}
return false;
}
}

View file

@ -6,6 +6,7 @@ import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -13,7 +14,7 @@ import java.util.concurrent.ConcurrentMap;
* public facade
*/
public class JsonReader {
private static final ConcurrentMap<Class<?>, JsonObjectReader<?>> readers = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
public static <T> T read(Class<T> type, InputStream reader) {
return read(type, new IoReader(reader));
@ -29,11 +30,11 @@ public class JsonReader {
// class.cast() does not work for primitives;
}
private static <T> JsonObjectReader<?> getReader(Class<T> type) {
private static <T> JsonValueReader<?> getReader(Class<T> type) {
return readers.get(type);
}
static <T> void register(Class<T> type, JsonObjectReader<T> objectReader) {
static <T> void register(Class<T> type, JsonValueReader<T> objectReader) {
readers.put(type, objectReader);
}
@ -58,5 +59,6 @@ public class JsonReader {
register(String.class, new StringReader());
register(LocalDateTime.class, new LocalDateTimeReader());
register(List.class, new ListReader());
register(Map.class, new MapReader());
}
}

View file

@ -2,6 +2,6 @@ package nl.sander.jsontoy2;
import java.io.Reader;
public interface JsonObjectReader<T> {
public interface JsonValueReader<T> {
T read(IoReader ioReader);
}

View file

@ -1,8 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonValueReader;
public class BooleanReader implements nl.sander.jsontoy2.JsonObjectReader<Boolean> {
public class BooleanReader implements JsonValueReader<Boolean> {
@Override
public Boolean read(IoReader ioReader) {
return ioReader.readBoolean();

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class ByteReader implements JsonObjectReader<Byte> {
public class ByteReader implements JsonValueReader<Byte> {
@Override
public Byte read(IoReader ioReader) {
return ioReader.readByte();

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class CharReader implements JsonObjectReader<Character> {
public class CharReader implements JsonValueReader<Character> {
@Override
public Character read(IoReader ioReader) {
return ioReader.readCharacter();

View file

@ -1,17 +1,12 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Date;
public class DateReader extends AbstractDatesReader<Date> implements JsonObjectReader<Date> {
public class DateReader extends AbstractDatesReader<Date> implements JsonValueReader<Date> {
@Override
public Date read(IoReader ioReader) {

View file

@ -1,11 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
import java.io.Reader;
public class DoubleReader implements JsonObjectReader<Double> {
public class DoubleReader implements JsonValueReader<Double> {
@Override
public Double read(IoReader ioReader) {
return ioReader.readDouble();

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class FloatReader implements JsonObjectReader<Float> {
public class FloatReader implements JsonValueReader<Float> {
@Override
public Float read(IoReader ioReader) {
return ioReader.readFloat();

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class IntegerReader implements JsonObjectReader<Integer> {
public class IntegerReader implements JsonValueReader<Integer> {
@Override
public Integer read(IoReader ioReader) {
return ioReader.readInteger();

View file

@ -1,12 +1,12 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
import java.util.List;
@SuppressWarnings("rawtypes")
public class ListReader implements JsonObjectReader<List> {
public class ListReader implements JsonValueReader<List> {
@Override
public List<?> read(IoReader ioReader) {
return ioReader.readList();

View file

@ -1,12 +1,12 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> implements JsonObjectReader<LocalDateTime> {
public class LocalDateTimeReader extends AbstractDatesReader<LocalDateTime> implements JsonValueReader<LocalDateTime> {
@Override
public LocalDateTime read(IoReader ioReader) {

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class LongReader implements JsonObjectReader<Long> {
public class LongReader implements JsonValueReader<Long> {
@Override
public Long read(IoReader ioReader) {
return ioReader.readLong();

View file

@ -0,0 +1,13 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonValueReader;
import java.util.Map;
public class MapReader implements JsonValueReader<Map> {
@Override
public Map read(IoReader ioReader) {
return ioReader.readMap();
}
}

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class ShortReader implements JsonObjectReader<Short> {
public class ShortReader implements JsonValueReader<Short> {
@Override
public Short read(IoReader ioReader) {

View file

@ -1,9 +1,9 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.IoReader;
import nl.sander.jsontoy2.JsonObjectReader;
import nl.sander.jsontoy2.JsonValueReader;
public class StringReader implements JsonObjectReader<String> {
public class StringReader implements JsonValueReader<String> {
@Override
public String read(IoReader ioReader) {

View file

@ -2,10 +2,7 @@ package nl.sander.jsontoy2;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -15,54 +12,61 @@ public class Lists {
@Test
public void emptyList() {
List<String> list = JsonReader.read(List.class, "[]");
assertEquals(new ArrayList<>(), list);
assertEquals(List.of(), list);
}
@Test
public void singleStringList() {
List<String> list = JsonReader.read(List.class, "[\"hello jason\"]");
assertEquals(Collections.singletonList("hello jason"), list);
assertEquals(List.of("hello jason"), list);
}
@Test
public void multipleStringList() {
List<String> list = JsonReader.read(List.class, "[\"hello\" , \"jason\"]");
List<String> expected = Arrays.asList("hello", "jason");
List<String> expected = List.of("hello", "jason");
assertEquals(expected, list);
}
@Test
public void multipleStrings_noComma_error() {
List<String> list = JsonReader.read(List.class, " [\"hello\" \"jason\"]");
List<String> expected = Collections.singletonList("hello");
List<String> expected = List.of("hello");
assertEquals(expected, list);
}
@Test
public void singleInt() {
List<Integer> list = JsonReader.read(List.class, " [ 1 ]");
List<Integer> expected = Collections.singletonList(1);
List<Integer> expected = List.of(1);
assertEquals(expected, list);
}
@Test
public void multipleInts() {
List<Integer> list = JsonReader.read(List.class, "[1,2]");
List<Integer> expected = Arrays.asList(1,2);
List<Integer> expected = List.of(1, 2);
assertEquals(expected, list);
}
@Test
public void intDoubleBooleanString() {
List<Integer> list = JsonReader.read(List.class, "[1, 2.5,false, \"hello jason\"]");
List<?> expected = Arrays.asList(1,2.5,false,"hello jason");
List<?> expected = List.of(1, 2.5, false, "hello jason");
assertEquals(expected, list);
}
@Test
public void nestedList() {
List<Integer> list = JsonReader.read(List.class, "[[],[]]");
List<?> expected = Arrays.asList(List.of(), List.of());
List<?> expected = List.of(List.of(), List.of());
assertEquals(expected, list);
}
@Test
public void mapInList() {
List<Object> list = JsonReader.read(List.class, "[[],{\"list\":[]]}]");
List<?> expected = List.of(List.of(), Map.of("list",List.of()));
assertEquals(expected, list);
}
}

View file

@ -0,0 +1,66 @@
package nl.sander.jsontoy2;
import org.junit.jupiter.api.Test;
import java.util.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@SuppressWarnings("unchecked")
public class Maps {
@Test
public void emptyMap() {
Map<Object, Object> map = JsonReader.read(Map.class, "{}");
assertEquals(new HashMap<>(), map);
}
@Test
public void singleStringKeyValueMap() {
Map<String, String> map = JsonReader.read(Map.class, "{\"message\": \"hello jason\"}");
assertEquals(Collections.singletonMap("message", "hello jason"), map);
}
@Test
public void multipleValues() {
Map<String, Object> map = JsonReader.read(Map.class, "{\"value1\" : \"jason\" ,\n \"value2\":1}");
Map<String, Object> expected = new HashMap<>();
expected.put("value1", "jason");
expected.put("value2", 1);
assertEquals(expected, map);
}
@Test
public void multipleStrings_noColon_error() {
assertThrows(JsonReadException.class, () -> JsonReader.read(Map.class, " {\"hello\" \"jason\"}"));
}
@Test
public void singleInts() {
Map<String,Integer> map = JsonReader.read(Map.class, " { \"1\":2 }");
Map<String,Integer> expected = Collections.singletonMap("1",2);
assertEquals(expected, map);
}
@Test
@SuppressWarnings("raw")
public void nestedMap() {
Map<String,Map> list = JsonReader.read(Map.class, "{\"map\": {\"map\":{}}}");
Map<String,Map> expected = new HashMap<>();
HashMap<String,Map> n1 = new HashMap<>();
n1.put("map",new HashMap<>());
expected.put("map", n1);
assertEquals(expected, list);
}
@Test
public void listInMap(){
Map<String,List> list = JsonReader.read(Map.class, " { \"list\" : [ 1 ] } ");
Map<String,List> expected = new HashMap<>();
expected.put("list", List.of(1));
assertEquals(expected, list);
}
}

View file

@ -2,12 +2,7 @@ package nl.sander.jsontoy2;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
@ -48,7 +43,7 @@ public class Strings {
@Test
public void escapedSingleQuote() {
assertThrows(JsonReadException.class, () -> JsonReader.read(String.class, "\"\\\'\""));
assertThrows(JsonReadException.class, () -> JsonReader.read(String.class, "\"\\'\""));
}
}

View file

@ -0,0 +1,51 @@
package nl.sander.jsontoy2;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import java.nio.charset.CharacterCodingException;
import static org.junit.jupiter.api.Assertions.*;
public class StringsWithJackson {
ObjectMapper jackson = new ObjectMapper();
@Test
public void regular() throws JsonProcessingException {
assertEquals("DADA", jackson.readValue("\"DADA\"", String.class));
}
@Test
public void firstSurrogateButSecondMissing() throws NoSuchFieldException, IllegalAccessException, JsonProcessingException {
String value =jackson.readValue("\"\\uDADA\"", String.class);
assertTrue(true);
}
@Test
public void incompleteSurrogateAndEscapeValid() throws JsonProcessingException {
assertThrows(JsonParseException.class, () -> jackson.readValue("\"\\uD800\n\"", String.class));
}
@Test
public void firstValidSurrogateSecondInvalid() throws JsonProcessingException {
String value =jackson.readValue("\"\\uD888\\u1334\"", String.class);
assertTrue(true);
}
@Test
public void escapedDoubleQuote() throws JsonProcessingException {
String value =jackson.readValue("\"\\\"\"", String.class);
assertEquals("\"", value);
}
@Test
public void escapedSingleQuote() {
assertThrows(JsonParseException.class, () -> jackson.readValue("\"\\'\"", String.class));
}
}