added java bean support

This commit is contained in:
Sander Hautvast 2020-08-05 08:58:31 +02:00
parent 75e40ae502
commit a8a364b557
46 changed files with 1241 additions and 100 deletions

View file

@ -59,9 +59,9 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.6.2</version>
<scope>test</scope>
</dependency>
<dependency>

View file

@ -0,0 +1,153 @@
package nl.sander.jsontoy2;
import javassist.*;
import nl.sander.jsontoy2.javassist.ClassCreationException;
import nl.sander.jsontoy2.javassist.Javassist;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class JavaObjectReaderFactory {
public static final String ROOT_PACKAGE = "serializer.";
private final static CtClass DESERIALIZER_BASE = Javassist.getTypeDefinition(JsonValueReader.class);
private static final CtClass[] NO_EXCEPTIONS = {};
private static final CtClass[] PARSER_PARAM = {Javassist.getTypeDefinition(Parser.class)};
private static final Class<?>[] NO_PARAMS = {};
private static final CtClass[] CT_NO_PARAMS = {};
private final static CtClass OBJECT_CLASS = Javassist.getTypeDefinition(Object.class);
private final static CtClass BOOLEAN = Javassist.getTypeDefinition(boolean.class);
private final static CtClass INT = Javassist.getTypeDefinition(int.class);
private final static CtClass LONG = Javassist.getTypeDefinition(long.class);
private final static CtClass BYTE = Javassist.getTypeDefinition(byte.class);
private final static CtClass SHORT = Javassist.getTypeDefinition(short.class);
private final static CtClass FLOAT = Javassist.getTypeDefinition(float.class);
private final static CtClass DOUBLE = Javassist.getTypeDefinition(double.class);
@SuppressWarnings("unchecked")
static <T> JsonValueReader<T> createReaderInstance(Class<T> type) {
try {
Class<?> jsonTypeReader = createReaderClass(type);
return (JsonValueReader<T>) jsonTypeReader.getDeclaredConstructor(NO_PARAMS).newInstance(new Object[]{});
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new JsonParseException(e);
}
}
private static Class<?> createReaderClass(Class<?> type) {
String name = createDeserializerName(type);
CtClass deserializerClass = Javassist.createClass(name, DESERIALIZER_BASE);
try {
deserializerClass.addConstructor(CtNewConstructor.make(CT_NO_PARAMS, NO_EXCEPTIONS, "{super();}", deserializerClass));
deserializerClass.addMethod(createReadJsonMethod(deserializerClass, type));
return deserializerClass.toClass();
} catch (CannotCompileException e) {
throw new ClassCreationException(e);
}
}
private static CtMethod createReadJsonMethod(CtClass serializerClass, Class<?> type) {
try {
String readMethodBodySource = createReadMethodBodySource(type);
System.out.println(readMethodBodySource);
return CtNewMethod.make(Modifier.PUBLIC, OBJECT_CLASS, "read", PARSER_PARAM, NO_EXCEPTIONS, readMethodBodySource, serializerClass);
} catch (CannotCompileException e) {
throw new ClassCreationException(e);
}
}
private static String createReadMethodBodySource(Class<?> type) {
String source = "{";
String typeName = type.getName();
if (ReaderFactory.readerSuppliers.containsKey(type)) {
source += "return " + JsonReader.class.getName() + ".read(" + typeName + ".class, $1);";
} else {
source += "java.util.Map object=" + JsonReader.class.getName() + ".readJavaObject(" + typeName + ".class,$1);\n";
source += typeName + " instance = new " + typeName + "();\n";
for (Field field : type.getDeclaredFields()) {
source += "instance.set" + capitalize(field.getName())
+ "(" + getSourceForGetValueFromObject(field) + ");\n";
}
source += "return instance;";
}
source += "}\n";
return source;
}
@NotNull
private static String getSourceForGetValueFromObject(Field field) {
Class<?> fieldType = field.getType();
String fieldName = field.getName();
if (fieldType == boolean.class) {
return "getBoolean(\"" + fieldName + "\", object)";
} else if (fieldType == int.class) {
return "getInt(\"" + fieldName + "\", object)";
} else if (fieldType == long.class) {
return "getLong(\"" + fieldName + "\",object)";
} else if (fieldType == short.class) {
return "getShort(\"" + fieldName + "\",object)";
} else if (fieldType == byte.class) {
return "getByte(\"" + fieldName + "\",object)";
} else if (fieldType == float.class) {
return "getFloat(\"" + fieldName + "\",object)";
} else if (fieldType == double.class) {
return "getDouble(\"" + fieldName + "\",object)";
} else if (Set.class.isAssignableFrom(fieldType)) {
return "getSet(\"" + fieldName + "\",object)";
} else {
return "(" + fieldType.getName() + ")(object.get(\"" + fieldName + "\"))";
}
}
private static String genericType(CtField field) {
try {
if (!Javassist.isCollection(field.getType())) {
return "";
} else {
String genericSignature = field.getGenericSignature(); // java.util.List<java.lang.String;>;
Pattern p = Pattern.compile("(.+?)<(.+?);>;");
Matcher matcher = p.matcher(genericSignature);
if (matcher.find()) {
String[] genericTypes = matcher.group(2).substring(1).replaceAll("/", ".").split(";L");
for (int i = 0; i < genericTypes.length; i++) {
genericTypes[i] += ".class";
}
return String.join(",", genericTypes);
}
throw new ClassCreationException("Generic type not ok");
}
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
private static String capitalize(String lowercase) {
return lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
}
private static Set<String> createTypeList(Class<?>... classes) {
return Arrays.stream(classes).map(Class::getName).collect(Collectors.toSet());
}
/*
* custom root package is prepended to avoid the java.lang class in which it's illegal to create new classes
*
* Array marks ( '[]' ) are replaced by the 'Array', Otherwise the SerializerClassName would be syntactically incorrect
*/
private static String createDeserializerName(Class<?> type) {
return ROOT_PACKAGE + type.getName().replaceAll("\\[]", "Array") + "Deserializer";
}
}

View file

@ -1,33 +1,21 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.readers.*;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
/**
* public api
*/
public class JsonReader {
private static final ConcurrentMap<Class<?>, Supplier<JsonValueReader<?>>> readSuppliers = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
private final static ThreadLocal<SoftReference<Parser>> PARSERS = new ThreadLocal<>();
static {
registerPrimitiveTypeReaders();
}
/**
* reads a value from a stream for a type that is not known beforehand
@ -43,21 +31,12 @@ public class JsonReader {
* array => List
*/
public static Object read(InputStream inputStream) {
final InputStream in = ensureBuffered(inputStream);
final InputStream in = ensureBufferedStream(inputStream);
try (Parser parser = getParser(in)) {
return read(parser);
}
}
private static InputStream ensureBuffered(InputStream inputStream) {
if (inputStream instanceof BufferedInputStream) {
return inputStream;
} else {
return new BufferedInputStream(inputStream);
}
}
/**
* Reads a value from a string for a type that is not known beforehand
*
@ -68,28 +47,6 @@ public class JsonReader {
return read(getParser(jsonString));
}
private static Parser getParser(String jsonString) {
return getParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
}
private static Parser getParser(InputStream inputStream) {
Objects.requireNonNull(inputStream, "File not found");
Parser parser;
SoftReference<Parser> parserReference = PARSERS.get();
if (parserReference == null || (parser = parserReference.get()) == null) {
parser = new Parser(inputStream);
parserReference = new SoftReference<>(parser);
PARSERS.set(parserReference);
} else {
parser.init(inputStream);
}
return parser;
}
static Object read(Parser parser) {
return parser.parseAny();
}
/**
* Reads a value from a stream for the given type
*
@ -117,41 +74,47 @@ public class JsonReader {
return read(type, getParser(jsonString));
}
private static Parser getParser(String jsonString) {
return getParser(new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)));
}
private static Parser getParser(InputStream inputStream) {
Objects.requireNonNull(inputStream, "File not found");
Parser parser;
SoftReference<Parser> parserReference = PARSERS.get();
if (parserReference == null || (parser = parserReference.get()) == null) {
parser = new Parser(inputStream);
parserReference = new SoftReference<>(parser);
PARSERS.set(parserReference);
} else {
parser.init(inputStream);
}
return parser;
}
static Object read(Parser parser) {
return parser.parseAny();
}
@SuppressWarnings("unchecked")
private static <T> T read(Class<T> type, Parser parser) {
return (T) getReader(type).read(parser);
public static <T> T read(Class<T> type, Parser parser) {
return (T) ReaderFactory.getReader(type).read(parser);
// class.cast() does not work well for primitives;
}
private static <T> JsonValueReader<?> getReader(Class<T> type) {
return readers.computeIfAbsent(type, k -> readSuppliers.get(k).get());
@SuppressWarnings("unused")
public static Map<?, ?> readJavaObject(Class<?> type, Parser parser) {
return parser.parseObject(type);
}
private static <T> void register(Class<T> type, Supplier<JsonValueReader<?>> objectReader) {
readSuppliers.put(type, objectReader);
private static InputStream ensureBufferedStream(InputStream inputStream) {
if (inputStream instanceof BufferedInputStream) {
return inputStream;
} else {
return new BufferedInputStream(inputStream);
}
}
private static void registerPrimitiveTypeReaders() {
register(Boolean.class, BooleanReader::new);
register(boolean.class, BooleanReader::new);
register(Integer.class, IntegerReader::new);
register(int.class, IntegerReader::new);
register(Long.class, LongReader::new);
register(long.class, LongReader::new);
register(Byte.class, ByteReader::new);
register(byte.class, ByteReader::new);
register(Short.class, ShortReader::new);
register(short.class, ShortReader::new);
register(Double.class, DoubleReader::new);
register(double.class, DoubleReader::new);
register(Float.class, FloatReader::new);
register(float.class, FloatReader::new);
register(Date.class, DateReader::new);
register(Character.class, CharReader::new);
register(char.class, CharReader::new);
register(String.class, StringReader::new);
register(LocalDateTime.class, LocalDateTimeReader::new);
register(List.class, ListReader::new);
register(Map.class, MapReader::new);
}
}

View file

@ -1,5 +1,57 @@
package nl.sander.jsontoy2;
public interface JsonValueReader<T> {
T read(Parser parser);
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Base class for generated readers
*
* @param <T> the type that the reader produces
*/
@SuppressWarnings("unused") /* these methods will be called from generated code*/
public abstract class JsonValueReader<T> {
public abstract T read(Parser parser);
protected boolean getBoolean(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? false : (Boolean) value;
}
protected int getInt(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0 : (Integer) value;
}
protected long getLong(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0L : (Long) value;
}
protected short getShort(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? (short) 0 : (Short) value;
}
protected byte getByte(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? (byte) 0 : (Byte) value;
}
protected float getFloat(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0F : (Float) value;
}
protected double getDouble(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? 0D : (Double) value;
}
protected Set<?> getSet(String fieldName, Map<String, ?> values) {
Object value = values.get(fieldName);
return value == null ? null : new HashSet<>((List<?>) value);
}
}

View file

@ -37,6 +37,10 @@ public class Lexer implements AutoCloseable {
}
}
void eatUntil(char until) {
eatUntil(new char[]{until});
}
void eatUntil(char... until) {
while (current > -1 && (!contains(until, current) | Character.isWhitespace(current))) {
advance();
@ -67,4 +71,9 @@ public class Lexer implements AutoCloseable {
throw new JsonParseException(e);
}
}
@SuppressWarnings("unused")
public byte current() {
return current;
}
}

View file

@ -38,7 +38,7 @@ public class Parser extends Lexer {
public Integer parseInteger() {
final String value = parseNumber();
return Double.valueOf(value).intValue();
return Integer.valueOf(value);
}
public Long parseLong() {
@ -125,6 +125,10 @@ public class Parser extends Lexer {
}
public Map<?, ?> parseObject() {
return parseObject(null);
}
public Map<?, ?> parseObject(Class<?> type) {
final HashMap<Object, Object> map = new HashMap<>();
skipWhitespace();
if (current != '{') {
@ -142,8 +146,13 @@ public class Parser extends Lexer {
throw new JsonParseException("expected colon");
}
skipWhitespace();
final Maybe<Object> maybeValue = parseValue();
final Maybe<Object> maybeValue;
try {
maybeValue = type == null ? parseValue() : parseValue(type.getDeclaredField(key).getType());
maybeValue.ifPresent(value -> map.put(key, value));
} catch (NoSuchFieldException e) {
throw new JsonParseException(e);
}
}
advance();
skipWhitespace();
@ -181,8 +190,8 @@ public class Parser extends Lexer {
break;
default: String numeric = parseNumber();
double doubleValue = Double.parseDouble(numeric);
if ((int) doubleValue == doubleValue) {
value = (int) doubleValue;
if ((long) doubleValue == doubleValue) {
value = (long) doubleValue;
} else {
value = doubleValue;
}
@ -190,6 +199,20 @@ public class Parser extends Lexer {
return Maybe.of(value);
}
private Maybe<Object> parseValue(Class<?> type) {
final Object value;
skipWhitespace();
JsonValueReader<?> reader = ReaderFactory.getReader(type);
if (reader != null) {
value = reader.read(this);
} else {
value = parseObject();
}
return Maybe.of(value);
}
private Object readNull() {
expect(() -> new JsonParseException("Expected 'null', encountered " + (char) current), 'n', 'u', 'l', 'l');
return null;

View file

@ -0,0 +1,66 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.readers.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
public class ReaderFactory {
static final ConcurrentMap<Class<?>, Supplier<JsonValueReader<?>>> readerSuppliers = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, JsonValueReader<?>> readers = new ConcurrentHashMap<>();
private final static MapReader MAPREADER = new MapReader();
private final static ListReader LISTREADER = new ListReader();
static {
registerPrimitiveTypeReaders();
}
public <T> void registerCustomReader(Class<T> type, JsonValueReader<T> reader) {
readers.put(type, reader);
}
static <T> void register(Class<T> type, Supplier<JsonValueReader<?>> objectReader) {
readerSuppliers.put(type, objectReader);
}
static <T> JsonValueReader<?> getReader(Class<T> type) {
if (Map.class.isAssignableFrom(type)) {
return MAPREADER;
} else if (List.class.isAssignableFrom(type) || Set.class.isAssignableFrom(type)) {
return LISTREADER;
} else {
return readers.computeIfAbsent(type, k -> {
Supplier<JsonValueReader<?>> jsonValueReaderSupplier = readerSuppliers.get(k);
return Optional.ofNullable(jsonValueReaderSupplier)
.orElseGet(() -> () -> JavaObjectReaderFactory.createReaderInstance(type))
.get();
});
}
}
private static void registerPrimitiveTypeReaders() {
register(Boolean.class, BooleanReader::new);
register(boolean.class, BooleanReader::new);
register(Integer.class, IntegerReader::new);
register(int.class, IntegerReader::new);
register(Long.class, LongReader::new);
register(long.class, LongReader::new);
register(Byte.class, ByteReader::new);
register(byte.class, ByteReader::new);
register(Short.class, ShortReader::new);
register(short.class, ShortReader::new);
register(Double.class, DoubleReader::new);
register(double.class, DoubleReader::new);
register(Float.class, FloatReader::new);
register(float.class, FloatReader::new);
register(Date.class, DateReader::new);
register(Character.class, CharReader::new);
register(char.class, CharReader::new);
register(String.class, StringReader::new);
register(LocalDateTime.class, LocalDateTimeReader::new);
}
}

View file

@ -0,0 +1,11 @@
package nl.sander.jsontoy2.javassist;
public class ClassCreationException extends RuntimeException {
public ClassCreationException(Throwable e) {
super(e);
}
public ClassCreationException(String message) {
super(message);
}
}

View file

@ -0,0 +1,183 @@
package nl.sander.jsontoy2.javassist;
import javassist.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static java.util.Arrays.asList;
/**
* Facade for javassist functions
*/
public class Javassist {
private static final String STRING = "java.lang.String";
private static final String BOOLEAN = "java.lang.Boolean";
private static final String CHARACTER = "java.lang.Character";
private static final String BYTE = "java.lang.Byte";
private static final String DOUBLE = "java.lang.Double";
private static final String FLOAT = "java.lang.Float";
private static final String LONG = "java.lang.Long";
private static final String SHORT = "java.lang.Short";
private static final String INTEGER = "java.lang.Integer";
private final static Set<String> wrappersAndString = new HashSet<>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER, STRING));
private final static Set<String> wrappers = new HashSet<>(asList(BOOLEAN, CHARACTER, BYTE, DOUBLE, FLOAT, LONG, SHORT, INTEGER));
private static final String COLLECTION = "java.util.Collection";
private static final String LIST = "java.util.List";
private static final String SET = "java.util.Set";
private static final String MAP = "java.util.Map";
private final static ClassPool pool = ClassPool.getDefault();
public static CtClass getTypeDefinition(Class<?> type) {
try {
return pool.get(type.getName());
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
@SuppressWarnings("unused")
public static CtClass getTypeDefinition(String type) {
try {
return pool.get(type);
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
public static CtClass createClass(String className, CtClass baseClass) {
return pool.makeClass(className, baseClass);
}
public static boolean isPrimitiveOrWrapperOrString(CtClass beanClass) {
return beanClass.isPrimitive() || wrappersAndString.contains(beanClass.getName());
}
public static boolean isBasicType(CtClass beanClass) {
return isPrimitiveOrWrapperOrString(beanClass) || isList(beanClass);
}
public static boolean isList(CtClass type) {
try {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET)) {
return true;
}
}
return false;
} catch (NotFoundException e) {
throw new IllegalStateException(e);
}
}
public static boolean isCollection(CtClass type) {
try {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(COLLECTION) || interfaze.getName().equals(LIST) || interfaze.getName().equals(SET) || interfaze.getName().equals(MAP)) {
return true;
}
}
return false;
} catch (NotFoundException e) {
throw new IllegalStateException(e);
}
}
public static boolean isMap(CtClass type) throws NotFoundException {
List<CtClass> interfaces = new ArrayList<>(asList(type.getInterfaces()));
interfaces.add(type);
for (CtClass interfaze : interfaces) {
if (interfaze.getName().equals(MAP)) {
return true;
}
}
return false;
}
/*
* Retrieves getter methods from a class
*/
public static List<CtMethod> getGetters(CtClass type) {
List<CtMethod> methods = new ArrayList<>();
List<CtField> fields = getAllFields(type);
for (CtField field : fields) {
try {
CtMethod method = type.getMethod(getGetterMethod(field), getDescription(field));
if (Modifier.isPublic(method.getModifiers())) {
methods.add(method);
}
} catch (NotFoundException n) {
// ignore
}
}
return methods;
}
private static String getGetterMethod(CtField field) {
return "get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);
}
private static List<CtField> getAllFields(CtClass beanClass) {
try {
List<CtField> allfields = new ArrayList<>(asList(beanClass.getDeclaredFields()));
if (beanClass.getSuperclass() != null) {
return getAllFields(beanClass.getSuperclass(), allfields);
}
return allfields;
} catch (NotFoundException e) {
throw new ClassCreationException(e);
}
}
private static List<CtField> getAllFields(CtClass beanClass, List<CtField> allfields) {
allfields.addAll(asList(beanClass.getDeclaredFields()));
return allfields;
}
private static String getDescription(CtField field) throws NotFoundException {
if (field.getType().isArray()) {
return "()[" + innerClassName(field.getType().getName()) + ";";
} else if (!field.getType().isPrimitive()) {
return "()" + innerClassName(field.getType().getName()) + ";";
} else {
return "()" + asPrimitive(field.getType().getName());
}
}
private static String innerClassName(String name) {
return "L" + name.replaceAll("\\.", "/").replaceAll("\\[]", "");
}
private static String asPrimitive(String name) {
switch (name) {
case "int":
return "I";
case "byte":
return "B";
case "float":
return "F";
case "long":
return "J";
case "boolean":
return "Z";
case "char":
return "C";
case "double":
return "D";
case "short":
return "S";
}
return "";
}
}

View file

@ -1,5 +1,7 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
@ -7,7 +9,7 @@ import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.function.Supplier;
public abstract class AbstractDatesReader<T> {
public abstract class AbstractDatesReader<T> extends JsonValueReader<T> {
private static final ZoneId zone = ZoneId.systemDefault();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class BooleanReader implements JsonValueReader<Boolean> {
public class BooleanReader extends JsonValueReader<Boolean> {
@Override
public Boolean read(Parser parser) {
return parser.parseBoolean();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class ByteReader implements JsonValueReader<Byte> {
public class ByteReader extends JsonValueReader<Byte> {
@Override
public Byte read(Parser parser) {
return parser.parseByte();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class CharReader implements JsonValueReader<Character> {
public class CharReader extends JsonValueReader<Character> {
@Override
public Character read(Parser parser) {
return parser.parseCharacter();

View file

@ -1,12 +1,11 @@
package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
import java.time.ZonedDateTime;
import java.util.Date;
public class DateReader extends AbstractDatesReader<Date> implements JsonValueReader<Date> {
public class DateReader extends AbstractDatesReader<Date> {
@Override
public Date read(Parser parser) {

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class DoubleReader implements JsonValueReader<Double> {
public class DoubleReader extends JsonValueReader<Double> {
@Override
public Double read(Parser parser) {
return parser.parseDouble();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class FloatReader implements JsonValueReader<Float> {
public class FloatReader extends JsonValueReader<Float> {
@Override
public Float read(Parser parser) {
return parser.parseFloat();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class IntegerReader implements JsonValueReader<Integer> {
public class IntegerReader extends JsonValueReader<Integer> {
@Override
public Integer read(Parser parser) {
return parser.parseInteger();

View file

@ -6,7 +6,7 @@ import nl.sander.jsontoy2.Parser;
import java.util.List;
@SuppressWarnings("rawtypes")
public class ListReader implements JsonValueReader<List> {
public class ListReader extends JsonValueReader<List> {
@Override
public List<?> read(Parser parser) {
return parser.parseArray();

View file

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

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class LongReader implements JsonValueReader<Long> {
public class LongReader extends JsonValueReader<Long> {
@Override
public Long read(Parser parser) {
return parser.parseLong();

View file

@ -6,7 +6,7 @@ import nl.sander.jsontoy2.Parser;
import java.util.Map;
@SuppressWarnings("rawtypes")
public class MapReader implements JsonValueReader<Map> {
public class MapReader extends JsonValueReader<Map> {
@Override
public Map read(Parser parser) {
return parser.parseObject();

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class ShortReader implements JsonValueReader<Short> {
public class ShortReader extends JsonValueReader<Short> {
@Override
public Short read(Parser parser) {

View file

@ -3,7 +3,7 @@ package nl.sander.jsontoy2.readers;
import nl.sander.jsontoy2.JsonValueReader;
import nl.sander.jsontoy2.Parser;
public class StringReader implements JsonValueReader<String> {
public class StringReader extends JsonValueReader<String> {
@Override
public String read(Parser parser) {

View file

@ -0,0 +1,13 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.beans.BooleanBean;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class BeanWithBoolean {
@Test
public void testTrue() {
assertEquals(new BooleanBean(true, true), JsonReader.read(BooleanBean.class, "{\"value\": true, \"value2\": true}"));
}
}

View file

@ -0,0 +1,147 @@
package nl.sander.jsontoy2;
import nl.sander.jsontoy2.beans.*;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.HashSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class WrapperObjectTests {
@Test
public void testNested() {
SimpleBean bean = JsonReader.read(SimpleBean.class, "{\"data1\": \"value1\",\"data2\": \"value2\"}");
assertEquals(SimpleBean.class, bean.getClass());
assertEquals("value1", bean.getData1());
assertEquals("value2", bean.getData2());
}
@Test
public void testBoolean() {
// BooleanBean trueBean = JsonReader.read(BooleanBean.class, "{\"value\": true}");
// assertTrue(trueBean.isValue());
// second call to read, must not recreate class definition, would not compile
// so this test implicitly tests caching function too
BooleanBean falseBean = JsonReader.read(BooleanBean.class, "{\"value2\": false}");
assertFalse(falseBean.isValue());
}
@Test
public void testString() {
StringBean stringBean = JsonReader.read(StringBean.class, "{\"value\": \"haha\"}");
assertEquals("haha", stringBean.getValue());
}
@Test
public void testInteger() {
IntegerBean integerBean = JsonReader.read(IntegerBean.class, "{\"value\": 1,\"value2\": 2}");
assertEquals(1, integerBean.getValue());
assertEquals(Integer.valueOf(2), integerBean.getValue2());
}
@Test
public void testLong() {
LongBean longBean = JsonReader.read(LongBean.class, "{\"value\": 100000000000,\"value2\": 100000000000}");
assertEquals(100000000000L, longBean.getValue());
assertEquals(Long.valueOf(100000000000L), longBean.getValue2());
}
@Test
public void testFloat() {
FloatBean floatBean = JsonReader.read(FloatBean.class, "{\"value\": 1.0,\"value2\": 100000000000}");
assertEquals(1.0F, floatBean.getValue(), 0.1F);
assertEquals(100000000000.0F, floatBean.getValue2(), 0.1F);
}
@Test
public void testDouble() {
DoubleBean doubleBean = JsonReader.read(DoubleBean.class, "{\"value\": 1.0,\"value2\": 100000000000}");
assertEquals(1.0D, doubleBean.getValue(), 0.1D);
assertEquals(100000000000.0D, doubleBean.getValue2(), 0.1D);
}
@Test
public void testShort() {
ShortBean shortBean = JsonReader.read(ShortBean.class, "{\"value\": 1,\"value2\": -11}");
assertEquals(1, shortBean.getValue());
assertEquals((short) -11, shortBean.getValue2().shortValue());
}
@Test
public void testByte() {
ByteBean byteBean = JsonReader.read(ByteBean.class, "{\"value\": 1,\"value2\": -11}");
assertEquals(1, byteBean.getValue());
assertEquals((byte) -11, byteBean.getValue2().shortValue());
}
@Test
public void testStringList() {
StringListBean listBean = JsonReader.read(StringListBean.class, "{\"value\": [\"a\",\"b\"]}");
assertEquals(Arrays.asList("a", "b"), listBean.getValue());
}
@Test
public void testStringSet() {
StringSetBean listBean = JsonReader.read(StringSetBean.class, "{\"value\": [\"a\",\"b\"]}");
assertEquals(new HashSet<>(Arrays.asList("a", "b")), listBean.getValue());
}
// @Test
// public void testIntegerList() {
// IntegerListBean listBean = JsonReader.read(IntegerListBean.class, "{\"value\": [1,22]}");
// assertEquals(Arrays.asList(1, 22), listBean.getValue());
// }
//
// @Test
// public void testCharacterList() {
// CharacterListBean listBean = JsonReader.read(CharacterListBean.class, "{\"value\": [\"a\", \"[\", \"^\"]}");
// assertEquals(Arrays.asList('a', '[', '^'), listBean.getValue());
// }
// @Test
// public void testShortList() {
// ShortListBean listBean = JsonReader.read(ShortListBean.class, "{\"value\": [-1,0,1]}");
// assertEquals(Arrays.asList((short) -1, (short) 0, (short) 1), listBean.getValue());
// }
@Test
public void testBooleanList() {
BooleanListBean listBean = JsonReader.read(BooleanListBean.class, "{\"value\": [true,false]}");
assertEquals(Arrays.asList(true, false), listBean.getValue());
}
// @Test
// public void testFloatList() {
// FloatListBean listBean = JsonReader.read(FloatListBean.class, "{\"value\": [-100.156,78.0]}");
// assertEquals(Arrays.asList(-100.156F, 78.0F), listBean.getValue());
// }
//
// @Test
// public void testByteList() {
// ByteListBean listBean = JsonReader.read(ByteListBean.class, "{\"value\": [-100,78]}");
// assertEquals(Arrays.asList((byte) -100, (byte) 78), listBean.getValue());
// }
//
// @Test
// public void testDoubleList() {
// DoubleListBean listBean = JsonReader.read(DoubleListBean.class, "{\"value\": [-100.156,78.0]}");
// assertEquals(Arrays.asList(-100.156D, 78.0D), listBean.getValue());
// }
@Test
public void testNestedBean() {
NestedBean nestedBean = JsonReader.read(NestedBean.class, "{\"value\": {\"value\": \"nested\"}}");
assertEquals(new NestedBean(new InnerBean("nested")), nestedBean);
}
@Test
public void testStringMap() {
StringMapBean actual = JsonReader.read(StringMapBean.class, "{\"map\": {\"a:\": \"b\", \"c:\" : \"d\" }}");
StringMapBean expected = new StringMapBean("a:", "b", "c:", "d");
assertEquals(expected, actual);
}
}

View file

@ -0,0 +1,48 @@
package nl.sander.jsontoy2.beans;
import java.util.Objects;
public class BooleanBean {
private boolean value;
private Boolean value2;
public BooleanBean() {
}
public BooleanBean(boolean value, Boolean value2) {
this.value = value;
this.value2 = value2;
}
public boolean isValue() {
return value;
}
public void setValue(boolean value) {
this.value = value;
}
public Boolean getValue2() {
return value2;
}
public void setValue2(Boolean value2) {
this.value2 = value2;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BooleanBean that = (BooleanBean) o;
return value == that.value &&
value2.equals(that.value2);
}
@Override
public int hashCode() {
return Objects.hash(value, value2);
}
}

View file

@ -0,0 +1,22 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class BooleanListBean {
private List<Boolean> value;
public List<Boolean> getValue() {
return value;
}
public void setValue(List<Boolean> value) {
this.value = value;
}
@Override
public String toString() {
return "BooleanListBean{" +
"value=" + value +
'}';
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class ByteBean {
private byte value;
private Byte value2;
public ByteBean() {
}
public byte getValue() {
return value;
}
public void setValue(byte value) {
this.value = value;
}
public Byte getValue2() {
return value2;
}
public void setValue2(Byte value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class ByteListBean {
private List<Byte> value;
public List<Byte> getValue() {
return value;
}
public void setValue(List<Byte> value) {
this.value = value;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class CharacterListBean {
private List<Character> value;
public List<Character> getValue() {
return value;
}
public void setValue(List<Character> value) {
this.value = value;
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class DoubleBean {
private double value;
private Double value2;
public DoubleBean() {
}
public double getValue() {
return value;
}
public void setValue(double value) {
this.value = value;
}
public Double getValue2() {
return value2;
}
public void setValue2(Double value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
import nl.sander.jsontoy2.JsonReader;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DoubleListBean {
private List<Double> value;
public List<Double> getValue() {
return value;
}
public void setValue(List<Double> value) {
this.value = value;
}
@Test
public void testTrue() {
assertEquals(true, JsonReader.read(Boolean.class, "true"));
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class FloatBean {
private float value;
private Float value2;
public FloatBean() {
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
public Float getValue2() {
return value2;
}
public void setValue2(Float value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class FloatListBean {
private List<Float> value;
public List<Float> getValue() {
return value;
}
public void setValue(List<Float> value) {
this.value = value;
}
}

View file

@ -0,0 +1,42 @@
package nl.sander.jsontoy2.beans;
import java.util.Objects;
public class InnerBean {
private String value;
public InnerBean() {
}
public InnerBean(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InnerBean innerBean = (InnerBean) o;
return value.equals(innerBean.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "InnerBean{" +
"value='" + value + '\'' +
'}';
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class IntegerBean {
private int value;
private Integer value2;
public IntegerBean() {
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public Integer getValue2() {
return value2;
}
public void setValue2(Integer value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class IntegerListBean {
private List<Integer> value;
public List<Integer> getValue() {
return value;
}
public void setValue(List<Integer> value) {
this.value = value;
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class LongBean {
private long value;
private Long value2;
public LongBean() {
}
public long getValue() {
return value;
}
public void setValue(long value) {
this.value = value;
}
public Long getValue2() {
return value2;
}
public void setValue2(Long value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,48 @@
package nl.sander.jsontoy2.beans;
import java.util.Objects;
public class NestedBean {
private InnerBean value;
private String empty;
public NestedBean() {
}
public NestedBean(InnerBean value) {
this.value = value;
}
public InnerBean getValue() {
return value;
}
public void setValue(InnerBean value) {
this.value = value;
}
public void setEmpty(String empty) {
this.empty = empty;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NestedBean that = (NestedBean) o;
return value.equals(that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
@Override
public String toString() {
return "NestedBean{" +
"value=" + value +
'}';
}
}

View file

@ -0,0 +1,25 @@
package nl.sander.jsontoy2.beans;
public class ShortBean {
private short value;
private Short value2;
public ShortBean() {
}
public short getValue() {
return value;
}
public void setValue(short value) {
this.value = value;
}
public Short getValue2() {
return value2;
}
public void setValue2(Short value2) {
this.value2 = value2;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class ShortListBean {
private List<Short> value;
public List<Short> getValue() {
return value;
}
public void setValue(List<Short> value) {
this.value = value;
}
}

View file

@ -0,0 +1,22 @@
package nl.sander.jsontoy2.beans;
public class SimpleBean {
String data1;
String data2;
public String getData1() {
return data1;
}
public void setData1(String data1) {
this.data1 = data1;
}
public String getData2() {
return data2;
}
public void setData2(String data2) {
this.data2 = data2;
}
}

View file

@ -0,0 +1,13 @@
package nl.sander.jsontoy2.beans;
public class StringBean {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.List;
public class StringListBean {
private List<String> value;
public List<String> getValue() {
return value;
}
public void setValue(List<String> value) {
this.value = value;
}
}

View file

@ -0,0 +1,46 @@
package nl.sander.jsontoy2.beans;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class StringMapBean {
private Map<String, String> map = new HashMap<>();
public StringMapBean() {
this.map = new HashMap<>();
}
public StringMapBean(String... keysAndValues) {
if (keysAndValues.length % 2 == 1) {
throw new IllegalArgumentException("uneven number of arguments is not allowed here");
}
for (int i = 0; i < keysAndValues.length; ) {
String key = keysAndValues[i++];
String value = keysAndValues[i++];
map.put(key, value);
}
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Map<String, String> getMap() {
return map;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StringMapBean that = (StringMapBean) o;
return map.equals(that.map);
}
@Override
public int hashCode() {
return Objects.hash(map);
}
}

View file

@ -0,0 +1,15 @@
package nl.sander.jsontoy2.beans;
import java.util.Set;
public class StringSetBean {
private Set<String> value;
public Set<String> getValue() {
return value;
}
public void setValue(Set<String> value) {
this.value = value;
}
}