From fa5ab830e86499e6c24a1e68d8e4d22a193bb3bd Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Sun, 20 May 2018 03:08:30 -0400 Subject: [PATCH 1/6] Fix for #123: Initial refactoring based on PR feedback --- .../main/java/com/owlike/genson/Genson.java | 14 +- .../java/com/owlike/genson/GensonBuilder.java | 9 +- .../owlike/genson/reflect/BeanDescriptor.java | 40 ++-- .../com/owlike/genson/reflect/Evolvable.java | 30 +++ .../genson/reflect/EvolvableHandler.java | 64 +++++++ .../genson/reflect/EvolvableObject.java | 29 +++ .../reflect/UnknownPropertyHandler.java | 56 ++++++ .../reflect/UnknownPropertyHandlerTest.java | 171 ++++++++++++++++++ 8 files changed, 398 insertions(+), 15 deletions(-) create mode 100644 genson/src/main/java/com/owlike/genson/reflect/Evolvable.java create mode 100644 genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java create mode 100644 genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java create mode 100644 genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java create mode 100644 genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java diff --git a/genson/src/main/java/com/owlike/genson/Genson.java b/genson/src/main/java/com/owlike/genson/Genson.java index 23cb7659..92122704 100644 --- a/genson/src/main/java/com/owlike/genson/Genson.java +++ b/genson/src/main/java/com/owlike/genson/Genson.java @@ -9,6 +9,7 @@ import com.owlike.genson.reflect.BeanDescriptor; import com.owlike.genson.reflect.BeanDescriptorProvider; import com.owlike.genson.reflect.RuntimePropertyFilter; +import com.owlike.genson.reflect.UnknownPropertyHandler; import com.owlike.genson.stream.*; /** @@ -72,6 +73,7 @@ public final class Genson { private final EncodingAwareReaderFactory readerFactory = new EncodingAwareReaderFactory(); private final Map, Object> defaultValues; private final RuntimePropertyFilter runtimePropertyFilter; + private final UnknownPropertyHandler unknownPropertyHandler; /** * The default constructor will use the default configuration provided by the {@link GensonBuilder}. @@ -81,7 +83,8 @@ public Genson() { this(_default.converterFactory, _default.beanDescriptorFactory, _default.skipNull, _default.htmlSafe, _default.aliasClassMap, _default.withClassMetadata, _default.strictDoubleParse, _default.indent, - _default.withMetadata, _default.failOnMissingProperty, _default.defaultValues, _default.runtimePropertyFilter); + _default.withMetadata, _default.failOnMissingProperty, _default.defaultValues, + _default.runtimePropertyFilter, _default.unknownPropertyHandler); } /** @@ -108,11 +111,13 @@ public Genson() { * @param failOnMissingProperty throw a JsonBindingException when a key in the json stream does not match a property in the Java Class. * @param defaultValues contains a mapping from the raw class to the default value that should be used when the property is missing. * @param runtimePropertyFilter is used to define what bean properties should be excluded from ser/de at runtime. + * @param unknownPropertyHandler is used to handle unknown properties during ser/de. */ public Genson(Factory> converterFactory, BeanDescriptorProvider beanDescProvider, boolean skipNull, boolean htmlSafe, Map> classAliases, boolean withClassMetadata, boolean strictDoubleParse, boolean indent, boolean withMetadata, boolean failOnMissingProperty, - Map, Object> defaultValues, RuntimePropertyFilter runtimePropertyFilter) { + Map, Object> defaultValues, RuntimePropertyFilter runtimePropertyFilter, + UnknownPropertyHandler unknownPropertyHandler) { this.converterFactory = converterFactory; this.beanDescriptorFactory = beanDescProvider; this.skipNull = skipNull; @@ -129,6 +134,7 @@ public Genson(Factory> converterFactory, BeanDescriptorProvider bea this.indent = indent; this.withMetadata = withClassMetadata || withMetadata; this.failOnMissingProperty = failOnMissingProperty; + this.unknownPropertyHandler = unknownPropertyHandler; } /** @@ -609,6 +615,10 @@ public RuntimePropertyFilter runtimePropertyFilter() { return runtimePropertyFilter; } + public UnknownPropertyHandler unknownPropertyHandler() { + return unknownPropertyHandler; + } + /** * @deprecated use GensonBuilder */ diff --git a/genson/src/main/java/com/owlike/genson/GensonBuilder.java b/genson/src/main/java/com/owlike/genson/GensonBuilder.java index 147712d0..5932f9c7 100644 --- a/genson/src/main/java/com/owlike/genson/GensonBuilder.java +++ b/genson/src/main/java/com/owlike/genson/GensonBuilder.java @@ -76,6 +76,7 @@ public class GensonBuilder { private final Map, Object> defaultValues = new HashMap, Object>(); private boolean failOnNullPrimitive = false; private RuntimePropertyFilter runtimePropertyFilter = RuntimePropertyFilter.noFilter; + private UnknownPropertyHandler unknownPropertyHandler; public GensonBuilder() { defaultValues.put(int.class, 0); @@ -739,6 +740,11 @@ public GensonBuilder useRuntimePropertyFilter(RuntimePropertyFilter filter) { return this; } + public GensonBuilder useUnknownPropertyHandler(UnknownPropertyHandler handler) { + this.unknownPropertyHandler = handler; + return this; + } + /** * Creates an instance of Genson. You may use this method as many times you want. It wont * change the state of the builder, in sense that the returned instance will have always the @@ -824,7 +830,8 @@ protected Genson create(Factory> converterFactory, Map> classAliases) { return new Genson(converterFactory, getBeanDescriptorProvider(), isSkipNull(), isHtmlSafe(), classAliases, withClassMetadata, - strictDoubleParse, indent, metadata, failOnMissingProperty, defaultValues, runtimePropertyFilter); + strictDoubleParse, indent, metadata, failOnMissingProperty, + defaultValues, runtimePropertyFilter, unknownPropertyHandler); } /** diff --git a/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java b/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java index 753754b9..0e17d46a 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java +++ b/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java @@ -1,12 +1,7 @@ package com.owlike.genson.reflect; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.function.Consumer; import com.owlike.genson.*; import com.owlike.genson.reflect.BeanCreator.BeanCreatorProperty; @@ -46,6 +41,7 @@ public class BeanDescriptor implements Converter { private final boolean _noArgCtr; private static final Object MISSING = new Object(); + // Used as a cache so we just copy it instead of recreating and assigning the default values private Object[] globalCreatorArgs; @@ -86,11 +82,16 @@ public boolean isWritable() { } public void serialize(T obj, ObjectWriter writer, Context ctx) { + RuntimePropertyFilter runtimePropertyFilter = ctx.genson.runtimePropertyFilter(); + UnknownPropertyHandler unknownPropertyHandler = ctx.genson.unknownPropertyHandler(); + writer.beginObject(); - RuntimePropertyFilter runtimePropertyFilter = ctx.genson.runtimePropertyFilter(); for (PropertyAccessor accessor : accessibleProperties) { if (runtimePropertyFilter.shouldInclude(accessor, ctx)) accessor.serialize(obj, writer, ctx); } + if (unknownPropertyHandler != null) { + unknownPropertyHandler.writeUnknownProperties(obj, writer, ctx); + } writer.endObject(); } @@ -110,8 +111,10 @@ public T deserialize(ObjectReader reader, Context ctx) { } public void deserialize(T into, ObjectReader reader, Context ctx) { + RuntimePropertyFilter runtimePropertyFilter = ctx.genson.runtimePropertyFilter(); + UnknownPropertyHandler unknownPropertyHandler = ctx.genson.unknownPropertyHandler(); + reader.beginObject(); - RuntimePropertyFilter runtimePropertyFilter = ctx.genson.runtimePropertyFilter(); for (; reader.hasNext(); ) { reader.next(); String propName = reader.name(); @@ -122,6 +125,8 @@ public void deserialize(T into, ObjectReader reader, Context ctx) { } else { reader.skipValue(); } + } else if (unknownPropertyHandler != null) { + unknownPropertyHandler.onUnknownProperty(into, propName, reader, ctx); } else if (failOnMissingProperty) throw missingPropertyException(propName); else reader.skipValue(); } @@ -130,9 +135,12 @@ public void deserialize(T into, ObjectReader reader, Context ctx) { protected T _deserWithCtrArgs(ObjectReader reader, Context ctx) { - List names = new ArrayList(); - List values = new ArrayList(); + List names = new ArrayList<>(); + List values = new ArrayList<>(); + List> unknownProperties = new ArrayList<>(); + RuntimePropertyFilter runtimePropertyFilter = ctx.genson.runtimePropertyFilter(); + UnknownPropertyHandler unknownPropertyHandler = ctx.genson.unknownPropertyHandler(); reader.beginObject(); for (; reader.hasNext(); ) { @@ -148,6 +156,9 @@ protected T _deserWithCtrArgs(ObjectReader reader, Context ctx) { } else { reader.skipValue(); } + } else if (unknownPropertyHandler != null) { + Consumer callback = unknownPropertyHandler.onUnknownProperty(null, propName, reader, ctx); + unknownProperties.add(callback); } else if (failOnMissingProperty) throw missingPropertyException(propName); else reader.skipValue(); } @@ -175,7 +186,12 @@ protected T _deserWithCtrArgs(ObjectReader reader, Context ctx) { T bean = ofClass.cast(creator.create(creatorArgs)); for (int i = 0; i < size; i++) { PropertyMutator property = mutableProperties.get(newNames[i]); - if (property != null) property.mutate(bean, newValues[i]); + if (property != null) { + property.mutate(bean, newValues[i]); + } + } + if (!unknownProperties.isEmpty()) { + unknownProperties.forEach(callback -> callback.accept(bean)); } reader.endObject(); return bean; diff --git a/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java new file mode 100644 index 00000000..43fd87f3 --- /dev/null +++ b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java @@ -0,0 +1,30 @@ +package com.owlike.genson.reflect; + +import java.util.Map; + +/** + * An interface that can be implemented by data classes + * in order to support schema evolution. + *

+ * This interface is used in combination with {@link EvolvableHandler} + * in order to prevent data loss during serialization across different + * versions of data classes. + * + * @author Aleksandar Seovic 2018.05.20 + */ +interface Evolvable { + /** + * Add unknown property to this instance. + * + * @param propName property name + * @param propValue property value + */ + void addUnknownProperty(String propName, Object propValue); + + /** + * Return a map of unknown properties. + * + * @return a map of unknown properties + */ + Map unknownProperties(); +} diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java new file mode 100644 index 00000000..9f028f26 --- /dev/null +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java @@ -0,0 +1,64 @@ +package com.owlike.genson.reflect; + +import com.owlike.genson.Context; +import com.owlike.genson.GenericType; +import com.owlike.genson.stream.ObjectReader; +import com.owlike.genson.stream.ObjectWriter; + +import java.util.Map; +import java.util.function.Consumer; + +/** + * An implementation of an {@link UnknownPropertyHandler} that supports + * evolution of data classes via {@link Evolvable} interface. + *

+ * If the target object we are deserializing into is {@link Evolvable}, + * this handler will add any unknown properties encountered during + * deserialization into {@link Evolvable#unknownProperties()} map, + * and will write them out along with all known properties during + * subsequent serialization. + *

+ * This prevents data loss when serializing and deserializing the same + * JSON payload using different versions of Java data classes. + * + * @author Aleksandar Seovic 2018.05.20 + */ +public class EvolvableHandler implements UnknownPropertyHandler { + private static final GenericType UNKNOWN = new GenericType() {}; + + @Override + public Consumer onUnknownProperty(T target, String propName, ObjectReader reader, Context ctx) { + // TODO: change this to read property as an opaque value, using ObjectReader directly + Object propValue = ctx.genson.deserialize(UNKNOWN, reader, ctx); + + if (target == null) { + // this is a bit ugly... + // the issue is that we may not have a target object while parsing JSON when using creators, + // so we need to store the parsed value somewhere and apply it later + return objTarget -> { + if (objTarget instanceof Evolvable) { + ((Evolvable) objTarget).addUnknownProperty(propName, propValue); + } + }; + } + + if (target instanceof Evolvable) { + ((Evolvable) target).addUnknownProperty(propName, propValue); + } + return null; + } + + @Override + public void writeUnknownProperties(T source, ObjectWriter writer, Context ctx) { + if (source instanceof Evolvable) { + Map props = ((Evolvable) source).unknownProperties(); + if (props != null) { + for (String propName : props.keySet()) { + writer.writeName(propName); + // TODO: change this to write property as an opaque value, using ObjectWriter directly + ctx.genson.serialize(props.get(propName), writer, ctx); + } + } + } + } +} \ No newline at end of file diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java new file mode 100644 index 00000000..4796b127 --- /dev/null +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java @@ -0,0 +1,29 @@ +package com.owlike.genson.reflect; + +import com.owlike.genson.annotation.JsonIgnore; + +import java.util.HashMap; +import java.util.Map; + +/** + * Convenience base class for {@link Evolvable} data classes. + * + * @author Aleksandar Seovic 2018.05.20 + */ +public abstract class EvolvableObject implements Evolvable { + @JsonIgnore + private Map unknownProperties; + + @Override + public void addUnknownProperty(String propName, Object propValue) { + if (unknownProperties == null) { + unknownProperties = new HashMap<>(); + } + unknownProperties.put(propName, propValue); + } + + @Override + public Map unknownProperties() { + return unknownProperties; + } +} diff --git a/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java b/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java new file mode 100644 index 00000000..c3bf8e0b --- /dev/null +++ b/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java @@ -0,0 +1,56 @@ +package com.owlike.genson.reflect; + +import com.owlike.genson.Context; +import com.owlike.genson.stream.ObjectReader; +import com.owlike.genson.stream.ObjectWriter; + +import java.util.function.Consumer; + +/** + * An interface that defines callbacks that will be called when an + * unknown properties are encountered during deserialization, as well + * as to check if there are any unknown properties that should be + * written out during serialization. + *

+ * The main purpose of this interface is to support schema evolution + * of objects that use JSON as a long term storage format, without + * loss of unknown properties across clients and severs using different + * versions of Java classes. + * + * @author Aleksandar Seovic 2018.05.09 + */ +public interface UnknownPropertyHandler { + /** + * Called whenever a property is encountered in a JSON document + * that doesn't have a corresponding {@link PropertyMutator}. + *

+ * Typically, the implementation of this interface concerned + * with schema evolution will handle this event by storing + * property value somewhere so it can be written later by the + * {@link #writeUnknownProperties} method. + * + * @param target the object we are deserializing JSON into, if known + * @param propName the name of the unknown property + * @param reader the ObjectReader to read property value from + * @param ctx deserialization context + * + * @return the optional Consumer that will be called once the target object is known + */ + Consumer onUnknownProperty(T target, String propName, ObjectReader reader, Context ctx); + + /** + * Write unknown properties encountered during deserialization. + *

+ * This method can be optionally implemented by {@code UnknownPropertyHandler}s + * that want to write unknown properties during serialization. The default + * implementation is a no-op. + * + * @param source the object we are serializing into JSON + * @param writer the ObjectReader to read property value from + * @param ctx serialization context + * + * @return a map of unknown properties + */ + default void writeUnknownProperties(T source, ObjectWriter writer, Context ctx) { + } +} diff --git a/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java b/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java new file mode 100644 index 00000000..8b28d78b --- /dev/null +++ b/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java @@ -0,0 +1,171 @@ +package com.owlike.genson.reflect; + +import com.owlike.genson.Genson; +import com.owlike.genson.GensonBuilder; + +import com.owlike.genson.annotation.JsonCreator; +import org.junit.Test; + +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; + +import static org.junit.Assert.assertEquals; + +/** + * @author Aleksandar Seovic 2018.05.09 + */ +public class UnknownPropertyHandlerTest { + private static final Genson GENSON = new GensonBuilder() + .useClassMetadata(true) + .useConstructorWithArguments(true) + .useUnknownPropertyHandler(new EvolvableHandler()) + .useIndentation(true) + .create(); + + @Test + public void testDeserialization() { + String json = "{\n" + + " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$EvolvablePerson\",\n" + + " \"age\":50,\n" + + " \"name\":\"Homer\",\n" + + " \"spouse\":{\n" + + " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$EvolvablePerson\",\n" + + " \"age\":40,\n" + + " \"name\":\"Marge\"\n" + + " },\n" + + " \"children\":[\n" + + " \"Bart\",\n" + + " \"Lisa\",\n" + + " \"Maggie\"\n" + + " ],\n" + + " \"salary\":10000.0,\n" + + " \"donutLover\":true\n" + + "}"; + + EvolvablePerson homer = GENSON.deserialize(json, EvolvablePerson.class); + assertEquals("Homer", homer.name); + assertEquals(50, homer.age); + assertEquals(Arrays.asList("Bart", "Lisa", "Maggie"), homer.unknownProperties.get("children")); + assertEquals(10_000d, homer.unknownProperties.get("salary")); + assertEquals(true, homer.unknownProperties.get("donutLover")); + } + + @Test + public void testCtorDeserialization() { + String json = "{\n" + + " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$CtorEvolvablePerson\",\n" + + " \"age\":50,\n" + + " \"name\":\"Homer\",\n" + + " \"spouse\":{\n" + + " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$CtorEvolvablePerson\",\n" + + " \"age\":40,\n" + + " \"name\":\"Marge\"\n" + + " },\n" + + " \"children\":[\n" + + " \"Bart\",\n" + + " \"Lisa\",\n" + + " \"Maggie\"\n" + + " ],\n" + + " \"salary\":10000.0,\n" + + " \"donutLover\":true\n" + + "}"; + + EvolvablePerson homer = GENSON.deserialize(json, CtorEvolvablePerson.class); + assertEquals("Homer", homer.name); + assertEquals(50, homer.age); + assertEquals(Arrays.asList("Bart", "Lisa", "Maggie"), homer.unknownProperties.get("children")); + assertEquals(10_000d, homer.unknownProperties.get("salary")); + assertEquals(true, homer.unknownProperties.get("donutLover")); + } + + @Test + public void testRoundTrip() { + EvolvablePerson homer = new EvolvablePerson("Homer", 50); + homer.unknownProperties().put("spouse", new EvolvablePerson("Marge", 40)); + homer.unknownProperties().put("children", Arrays.asList("Bart", "Lisa", "Maggie")); + homer.unknownProperties().put("salary", 10_000d); + homer.unknownProperties().put("donutLover", true); + + String json = GENSON.serialize(homer); + EvolvablePerson homer2 = GENSON.deserialize(json, EvolvablePerson.class); + + assertEquals(homer, homer2); + } + + static class EvolvablePerson implements Evolvable { + private Map unknownProperties = new LinkedHashMap<>(); + private String name; + private int age; + + public EvolvablePerson() { + } + + public EvolvablePerson(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getAge() { + return age; + } + + public void setAge(int age) { + this.age = age; + } + + @Override + public void addUnknownProperty(String propName, Object propValue) { + unknownProperties.put(propName, propValue); + } + + @Override + public Map unknownProperties() { + return unknownProperties; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EvolvablePerson that = (EvolvablePerson) o; + return age == that.age && + Objects.equals(unknownProperties, that.unknownProperties) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(unknownProperties, name, age); + } + + @Override + public String toString() { + return "EvolvablePerson{" + + "name='" + name + '\'' + + ", age=" + age + + ", unknownProperties=" + unknownProperties + + '}'; + } + } + + static class CtorEvolvablePerson extends EvolvablePerson { + private CtorEvolvablePerson() { + throw new RuntimeException("shouldn't be called"); + } + + @JsonCreator + public CtorEvolvablePerson(String name, int age) { + super(name, age); + } + } +} From 1617dbf30e23f3bd1cd7bad747a2ff0c087272cd Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Mon, 21 May 2018 03:32:50 -0400 Subject: [PATCH 2/6] Fix for #123: Added support for missing classes via JsonValue --- .../convert/ClassMetadataConverter.java | 14 +- .../genson/ext/jsr353/JSR353Bundle.java | 7 +- .../genson/reflect/EvolvableHandler.java | 3 +- .../com/owlike/genson/stream/JsonReader.java | 6 + .../com/owlike/genson/stream/JsonWriter.java | 27 +-- .../owlike/genson/stream/ObjectReader.java | 17 +- .../reflect/UnknownPropertyHandlerTest.java | 188 +++++++++++------- 7 files changed, 156 insertions(+), 106 deletions(-) diff --git a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java index 04e922fd..ede43ad8 100644 --- a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java +++ b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java @@ -4,7 +4,7 @@ import com.owlike.genson.*; import com.owlike.genson.annotation.HandleClassMetadata; -import com.owlike.genson.convert.DefaultConverters.UntypedConverterFactory.UntypedConverter; +import com.owlike.genson.ext.jsr353.JSR353Bundle; import com.owlike.genson.reflect.TypeUtil; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; @@ -65,18 +65,16 @@ protected Converter create(Type type, Genson genson, Converter nextConvert private final boolean classMetadataWithStaticType; private final Class tClass; - private final boolean skipMetadataSerialization; public ClassMetadataConverter(Class tClass, Converter delegate, boolean classMetadataWithStaticType) { super(delegate); this.tClass = tClass; this.classMetadataWithStaticType = classMetadataWithStaticType; - skipMetadataSerialization = Wrapper.isOfType(delegate, UntypedConverter.class); } public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception { - if (!skipMetadataSerialization && obj != null && - (classMetadataWithStaticType || (!classMetadataWithStaticType && !tClass.equals(obj.getClass())))) { + if (obj != null && !isJsonValueConverter(wrapped) && + (classMetadataWithStaticType || !tClass.equals(obj.getClass()))) { writer.beginNextObjectMetadata() .writeMetadata("class", ctx.genson.aliasFor(obj.getClass())); } @@ -84,7 +82,7 @@ public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception } public T deserialize(ObjectReader reader, Context ctx) throws Exception { - if (ValueType.OBJECT.equals(reader.getValueType())) { + if (ValueType.OBJECT.equals(reader.getValueType()) && !isJsonValueConverter(wrapped)) { String className = reader.nextObjectMetadata().metadata("class"); if (className != null) { try { @@ -101,4 +99,8 @@ public T deserialize(ObjectReader reader, Context ctx) throws Exception { } return wrapped.deserialize(reader, ctx); } + + private boolean isJsonValueConverter(Converter converter) { + return converter instanceof JSR353Bundle.JsonValueConverter; + } } diff --git a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java index 6b523c54..46bb3c81 100644 --- a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java +++ b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java @@ -101,7 +101,12 @@ public JsonValue deserialize(ObjectReader reader, Context ctx) { public JsonValue deserObject(ObjectReader reader, Context ctx) { JsonObjectBuilder builder = factory.createObjectBuilder(); - reader.beginObject(); + + // copy metadata first + Map metadata = reader.metadata(); + for (String key : metadata.keySet()) { + builder.add('@' + key, metadata.get(key)); + } while (reader.hasNext()) { com.owlike.genson.stream.ValueType type = reader.next(); diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java index 9f028f26..b77f562f 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java @@ -5,6 +5,7 @@ import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; +import javax.json.JsonValue; import java.util.Map; import java.util.function.Consumer; @@ -24,7 +25,7 @@ * @author Aleksandar Seovic 2018.05.20 */ public class EvolvableHandler implements UnknownPropertyHandler { - private static final GenericType UNKNOWN = new GenericType() {}; + private static final GenericType UNKNOWN = new GenericType() {}; @Override public Consumer onUnknownProperty(T target, String propName, ObjectReader reader, Context ctx) { diff --git a/genson/src/main/java/com/owlike/genson/stream/JsonReader.java b/genson/src/main/java/com/owlike/genson/stream/JsonReader.java index f17373ee..431eaf13 100644 --- a/genson/src/main/java/com/owlike/genson/stream/JsonReader.java +++ b/genson/src/main/java/com/owlike/genson/stream/JsonReader.java @@ -5,6 +5,7 @@ import java.io.StringReader; import java.util.ArrayDeque; import java.util.Arrays; +import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Map; @@ -286,6 +287,11 @@ public byte[] valueAsByteArray() { + valueType); } + public Map metadata() { + if (!_metadata_readen) nextObjectMetadata(); + return Collections.unmodifiableMap(_metadata); + } + public String metadata(String name) { if (!_metadata_readen) nextObjectMetadata(); return _metadata.get(name); diff --git a/genson/src/main/java/com/owlike/genson/stream/JsonWriter.java b/genson/src/main/java/com/owlike/genson/stream/JsonWriter.java index 48b538fb..e2b025c0 100644 --- a/genson/src/main/java/com/owlike/genson/stream/JsonWriter.java +++ b/genson/src/main/java/com/owlike/genson/stream/JsonWriter.java @@ -3,9 +3,9 @@ import java.io.IOException; import java.io.Writer; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; -import java.util.List; +import java.util.Map; +import java.util.LinkedHashMap; public class JsonWriter implements ObjectWriter { /* @@ -62,18 +62,7 @@ public class JsonWriter implements ObjectWriter { private final int _bufferSize = _buffer.length; private int _len = 0; - List _metadata = new ArrayList(); - - private class MetadataPair { - final String name; - final String value; - - public MetadataPair(String name, String value) { - super(); - this.name = name; - this.value = value; - } - } + Map _metadata = new LinkedHashMap<>(); public JsonWriter(Writer writer) { this(writer, false, false, false); @@ -122,10 +111,10 @@ public JsonWriter beginObject() { if (_ctx.peek() == JsonType.METADATA) { _ctx.pop(); begin(JsonType.OBJECT, '{'); - for (MetadataPair pair : _metadata) { - writeName('@' + pair.name); + for (String name : _metadata.keySet()) { + writeName('@' + name); beforeValue(); - writeInternalString(pair.value); + writeInternalString(_metadata.get(name)); } } else begin(JsonType.OBJECT, '{'); return this; @@ -500,7 +489,9 @@ public ObjectWriter beginNextObjectMetadata() { } public ObjectWriter writeMetadata(String name, String value) { - if (_ctx.peek() == JsonType.METADATA) _metadata.add(new MetadataPair(name, value)); + if (_ctx.peek() == JsonType.METADATA) { + _metadata.put(name, value); + } else if (_ctx.peek() == JsonType.OBJECT) { writeName('@' + name); writeValue(value); diff --git a/genson/src/main/java/com/owlike/genson/stream/ObjectReader.java b/genson/src/main/java/com/owlike/genson/stream/ObjectReader.java index ea0c53d3..ec3eceb5 100644 --- a/genson/src/main/java/com/owlike/genson/stream/ObjectReader.java +++ b/genson/src/main/java/com/owlike/genson/stream/ObjectReader.java @@ -1,7 +1,7 @@ package com.owlike.genson.stream; import java.io.Closeable; -import java.io.IOException; +import java.util.Map; /** * ObjectReader is part of the streaming api, it's implementations allow you to read data from the @@ -161,8 +161,19 @@ public interface ObjectReader extends Closeable { ValueType getValueType(); /** - * @param name the name of the metadata to retrieve. - * @return value of metadata with name as key or null if there is no such metadata. + * Return the map containing all metadata. + * + * @return the map containing all metadata key/value pairs. + * @throws JsonStreamException + */ + Map metadata(); + + /** + * The value of a specified metadata attribute. + * + * @param name the name of the metadata attribute to retrieve. + * @return the value of metadata with name as key or null if there + * is no such metadata attribute. * @throws JsonStreamException */ String metadata(String name); diff --git a/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java b/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java index 8b28d78b..dc9cc6f7 100644 --- a/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java +++ b/genson/src/test/java/com/owlike/genson/reflect/UnknownPropertyHandlerTest.java @@ -4,21 +4,25 @@ import com.owlike.genson.GensonBuilder; import com.owlike.genson.annotation.JsonCreator; +import com.owlike.genson.ext.jsr353.JSR353Bundle; + import org.junit.Test; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Objects; +import javax.json.JsonArray; +import javax.json.JsonObject; +import java.util.*; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * @author Aleksandar Seovic 2018.05.09 */ public class UnknownPropertyHandlerTest { private static final Genson GENSON = new GensonBuilder() + .withBundle(new JSR353Bundle()) .useClassMetadata(true) + .useClassMetadataWithStaticType(false) .useConstructorWithArguments(true) .useUnknownPropertyHandler(new EvolvableHandler()) .useIndentation(true) @@ -26,83 +30,74 @@ public class UnknownPropertyHandlerTest { @Test public void testDeserialization() { - String json = "{\n" + - " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$EvolvablePerson\",\n" + - " \"age\":50,\n" + - " \"name\":\"Homer\",\n" + - " \"spouse\":{\n" + - " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$EvolvablePerson\",\n" + - " \"age\":40,\n" + - " \"name\":\"Marge\"\n" + - " },\n" + - " \"children\":[\n" + - " \"Bart\",\n" + - " \"Lisa\",\n" + - " \"Maggie\"\n" + - " ],\n" + - " \"salary\":10000.0,\n" + - " \"donutLover\":true\n" + - "}"; + PersonV2 homer = new PersonV2("Homer", 50); + homer.setSpouse(new PersonV2("Marge", 40)); + homer.setChildren(Arrays.asList("Bart", "Lisa", "Maggie")); + + String json = GENSON.serialize(homer); + PersonV1 homerV1 = GENSON.deserialize(json, PersonV1.class); - EvolvablePerson homer = GENSON.deserialize(json, EvolvablePerson.class); - assertEquals("Homer", homer.name); - assertEquals(50, homer.age); - assertEquals(Arrays.asList("Bart", "Lisa", "Maggie"), homer.unknownProperties.get("children")); - assertEquals(10_000d, homer.unknownProperties.get("salary")); - assertEquals(true, homer.unknownProperties.get("donutLover")); + assertEquals("Homer", homerV1.name); + assertEquals(50, homerV1.age); + assertTrue(homerV1.unknownProperties().get("spouse") instanceof JsonObject); + assertTrue(((JsonObject) homerV1.unknownProperties().get("spouse")).containsKey("@class")); + assertTrue(homerV1.unknownProperties().get("children") instanceof JsonArray); } @Test public void testCtorDeserialization() { - String json = "{\n" + - " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$CtorEvolvablePerson\",\n" + - " \"age\":50,\n" + - " \"name\":\"Homer\",\n" + - " \"spouse\":{\n" + - " \"@class\":\"com.owlike.genson.reflect.UnknownPropertyHandlerTest$CtorEvolvablePerson\",\n" + - " \"age\":40,\n" + - " \"name\":\"Marge\"\n" + - " },\n" + - " \"children\":[\n" + - " \"Bart\",\n" + - " \"Lisa\",\n" + - " \"Maggie\"\n" + - " ],\n" + - " \"salary\":10000.0,\n" + - " \"donutLover\":true\n" + - "}"; + PersonV2 homer = new PersonV2("Homer", 50); + homer.setSpouse(new PersonV2("Marge", 40)); + homer.setChildren(Arrays.asList("Bart", "Lisa", "Maggie")); - EvolvablePerson homer = GENSON.deserialize(json, CtorEvolvablePerson.class); - assertEquals("Homer", homer.name); - assertEquals(50, homer.age); - assertEquals(Arrays.asList("Bart", "Lisa", "Maggie"), homer.unknownProperties.get("children")); - assertEquals(10_000d, homer.unknownProperties.get("salary")); - assertEquals(true, homer.unknownProperties.get("donutLover")); + String json = GENSON.serialize(homer); + PersonV1 homerV1 = GENSON.deserialize(json, CtorPersonV1.class); + + assertEquals("Homer", homerV1.name); + assertEquals(50, homerV1.age); + assertTrue(homerV1.unknownProperties().get("spouse") instanceof JsonObject); + assertTrue(((JsonObject) homerV1.unknownProperties().get("spouse")).containsKey("@class")); + assertTrue(homerV1.unknownProperties().get("children") instanceof JsonArray); } @Test public void testRoundTrip() { - EvolvablePerson homer = new EvolvablePerson("Homer", 50); - homer.unknownProperties().put("spouse", new EvolvablePerson("Marge", 40)); - homer.unknownProperties().put("children", Arrays.asList("Bart", "Lisa", "Maggie")); - homer.unknownProperties().put("salary", 10_000d); - homer.unknownProperties().put("donutLover", true); + PersonV2 homer = new PersonV2("Homer", 50); + homer.setSpouse(new PersonV2("Marge", 40)); + homer.setChildren(Arrays.asList("Bart", "Lisa", "Maggie")); String json = GENSON.serialize(homer); - EvolvablePerson homer2 = GENSON.deserialize(json, EvolvablePerson.class); + PersonV1 homerV1 = GENSON.deserialize(json, PersonV1.class); + + json = GENSON.serialize(homerV1); + PersonV2 homerV2 = GENSON.deserialize(json, PersonV2.class); - assertEquals(homer, homer2); + assertEquals(homer, homerV2); } - static class EvolvablePerson implements Evolvable { - private Map unknownProperties = new LinkedHashMap<>(); + @Test + public void testRoundTripWithMissingClass() { + String v3 = "{\n" + + " \"age\":50,\n" + + " \"name\":\"Homer\",\n" + + " \"address\":{\n" + + " \"@class\":\"com.missing.Address\",\n" + + " \"city\":\"Springfield\"\n" + + " }\n" + + "}"; + + PersonV1 homerV1 = GENSON.deserialize(v3, PersonV1.class); + assertEquals(v3, GENSON.serialize(homerV1)); + } + + static class PersonV1 extends EvolvableObject { private String name; private int age; - public EvolvablePerson() { + public PersonV1() { } - public EvolvablePerson(String name, int age) { + public PersonV1(String name, int age) { this.name = name; this.age = age; } @@ -124,47 +119,86 @@ public void setAge(int age) { } @Override - public void addUnknownProperty(String propName, Object propValue) { - unknownProperties.put(propName, propValue); + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PersonV1 that = (PersonV1) o; + return age == that.age && Objects.equals(name, that.name); } @Override - public Map unknownProperties() { - return unknownProperties; + public int hashCode() { + return Objects.hash(name, age); + } + + @Override + public String toString() { + return "PersonV1{" + + "name='" + name + '\'' + + ", age=" + age + + ", unknownProperties=" + unknownProperties() + + '}'; + } + } + + static class PersonV2 extends PersonV1 { + private Object spouse; + private List children; + + public PersonV2(String name, int age) { + super(name, age); + } + + public Object getSpouse() { + return spouse; + } + + public void setSpouse(Object spouse) { + this.spouse = spouse; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - EvolvablePerson that = (EvolvablePerson) o; - return age == that.age && - Objects.equals(unknownProperties, that.unknownProperties) && - Objects.equals(name, that.name); + if (!super.equals(o)) return false; + PersonV2 personV2 = (PersonV2) o; + return Objects.equals(spouse, personV2.spouse) && + Objects.equals(children, personV2.children); } @Override public int hashCode() { - return Objects.hash(unknownProperties, name, age); + return Objects.hash(super.hashCode(), spouse, children); } @Override public String toString() { - return "EvolvablePerson{" + - "name='" + name + '\'' + - ", age=" + age + - ", unknownProperties=" + unknownProperties + + return "PersonV2{" + + "name='" + getName() + '\'' + + ", age=" + getAge() + + ", spouse=" + spouse + + ", children=" + children + + ", unknownProperties=" + unknownProperties() + '}'; } } - static class CtorEvolvablePerson extends EvolvablePerson { - private CtorEvolvablePerson() { + static class CtorPersonV1 extends PersonV1 { + private CtorPersonV1() { throw new RuntimeException("shouldn't be called"); } @JsonCreator - public CtorEvolvablePerson(String name, int age) { + public CtorPersonV1(String name, int age) { super(name, age); } } From 7028dc7122ab32139681a1c0f8805a8618e0caa1 Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Mon, 21 May 2018 17:26:10 -0400 Subject: [PATCH 3/6] Fix for #123: Refactored unknown property consumption and switched to using JsonValueConverter directly --- .../genson/ext/jsr353/JSR353Bundle.java | 2 +- .../owlike/genson/reflect/BeanDescriptor.java | 9 ++-- .../com/owlike/genson/reflect/Evolvable.java | 5 ++- .../genson/reflect/EvolvableHandler.java | 42 +++++++++---------- .../genson/reflect/EvolvableObject.java | 7 ++-- .../reflect/UnknownPropertyHandler.java | 12 +++--- 6 files changed, 37 insertions(+), 40 deletions(-) diff --git a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java index 46bb3c81..14f4a64f 100644 --- a/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java +++ b/genson/src/main/java/com/owlike/genson/ext/jsr353/JSR353Bundle.java @@ -37,7 +37,7 @@ public Converter create(Type type, Genson genson) { }); } - public class JsonValueConverter implements Converter { + public static class JsonValueConverter implements Converter { @Override public void serialize(JsonValue value, ObjectWriter writer, Context ctx) { diff --git a/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java b/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java index 0e17d46a..d6531d94 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java +++ b/genson/src/main/java/com/owlike/genson/reflect/BeanDescriptor.java @@ -126,7 +126,7 @@ public void deserialize(T into, ObjectReader reader, Context ctx) { reader.skipValue(); } } else if (unknownPropertyHandler != null) { - unknownPropertyHandler.onUnknownProperty(into, propName, reader, ctx); + unknownPropertyHandler.readUnknownProperty(propName, reader, ctx).accept(into); } else if (failOnMissingProperty) throw missingPropertyException(propName); else reader.skipValue(); } @@ -157,7 +157,7 @@ protected T _deserWithCtrArgs(ObjectReader reader, Context ctx) { reader.skipValue(); } } else if (unknownPropertyHandler != null) { - Consumer callback = unknownPropertyHandler.onUnknownProperty(null, propName, reader, ctx); + Consumer callback = unknownPropertyHandler.readUnknownProperty(propName, reader, ctx); unknownProperties.add(callback); } else if (failOnMissingProperty) throw missingPropertyException(propName); else reader.skipValue(); @@ -190,9 +190,8 @@ protected T _deserWithCtrArgs(ObjectReader reader, Context ctx) { property.mutate(bean, newValues[i]); } } - if (!unknownProperties.isEmpty()) { - unknownProperties.forEach(callback -> callback.accept(bean)); - } + unknownProperties.forEach(callback -> callback.accept(bean)); + reader.endObject(); return bean; } diff --git a/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java index 43fd87f3..83f03dd7 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java +++ b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java @@ -1,5 +1,6 @@ package com.owlike.genson.reflect; +import javax.json.JsonValue; import java.util.Map; /** @@ -19,12 +20,12 @@ interface Evolvable { * @param propName property name * @param propValue property value */ - void addUnknownProperty(String propName, Object propValue); + void addUnknownProperty(String propName, JsonValue propValue); /** * Return a map of unknown properties. * * @return a map of unknown properties */ - Map unknownProperties(); + Map unknownProperties(); } diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java index b77f562f..c344af8e 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java @@ -1,11 +1,13 @@ package com.owlike.genson.reflect; import com.owlike.genson.Context; -import com.owlike.genson.GenericType; +import com.owlike.genson.Converter; +import com.owlike.genson.ext.jsr353.JSR353Bundle; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; import javax.json.JsonValue; + import java.util.Map; import java.util.function.Consumer; @@ -25,41 +27,37 @@ * @author Aleksandar Seovic 2018.05.20 */ public class EvolvableHandler implements UnknownPropertyHandler { - private static final GenericType UNKNOWN = new GenericType() {}; + private static final Converter CONVERTER = new JSR353Bundle.JsonValueConverter(); @Override - public Consumer onUnknownProperty(T target, String propName, ObjectReader reader, Context ctx) { - // TODO: change this to read property as an opaque value, using ObjectReader directly - Object propValue = ctx.genson.deserialize(UNKNOWN, reader, ctx); + public Consumer readUnknownProperty(String propName, ObjectReader reader, Context ctx) { + try { + JsonValue propValue = CONVERTER.deserialize(reader, ctx); - if (target == null) { - // this is a bit ugly... - // the issue is that we may not have a target object while parsing JSON when using creators, - // so we need to store the parsed value somewhere and apply it later return objTarget -> { if (objTarget instanceof Evolvable) { ((Evolvable) objTarget).addUnknownProperty(propName, propValue); } }; + } catch (Exception e) { + throw new RuntimeException(e); } - - if (target instanceof Evolvable) { - ((Evolvable) target).addUnknownProperty(propName, propValue); - } - return null; } @Override - public void writeUnknownProperties(T source, ObjectWriter writer, Context ctx) { - if (source instanceof Evolvable) { - Map props = ((Evolvable) source).unknownProperties(); - if (props != null) { - for (String propName : props.keySet()) { - writer.writeName(propName); - // TODO: change this to write property as an opaque value, using ObjectWriter directly - ctx.genson.serialize(props.get(propName), writer, ctx); + public void writeUnknownProperties(T bean, ObjectWriter writer, Context ctx) { + try { + if (bean instanceof Evolvable) { + Map props = ((Evolvable) bean).unknownProperties(); + if (props != null) { + for (String propName : props.keySet()) { + writer.writeName(propName); + CONVERTER.serialize(props.get(propName), writer, ctx); + } } } + } catch (Exception e) { + throw new RuntimeException(e); } } } \ No newline at end of file diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java index 4796b127..7256cfdb 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableObject.java @@ -2,6 +2,7 @@ import com.owlike.genson.annotation.JsonIgnore; +import javax.json.JsonValue; import java.util.HashMap; import java.util.Map; @@ -12,10 +13,10 @@ */ public abstract class EvolvableObject implements Evolvable { @JsonIgnore - private Map unknownProperties; + private Map unknownProperties; @Override - public void addUnknownProperty(String propName, Object propValue) { + public void addUnknownProperty(String propName, JsonValue propValue) { if (unknownProperties == null) { unknownProperties = new HashMap<>(); } @@ -23,7 +24,7 @@ public void addUnknownProperty(String propName, Object propValue) { } @Override - public Map unknownProperties() { + public Map unknownProperties() { return unknownProperties; } } diff --git a/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java b/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java index c3bf8e0b..aa6450eb 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java +++ b/genson/src/main/java/com/owlike/genson/reflect/UnknownPropertyHandler.java @@ -29,14 +29,14 @@ public interface UnknownPropertyHandler { * property value somewhere so it can be written later by the * {@link #writeUnknownProperties} method. * - * @param target the object we are deserializing JSON into, if known * @param propName the name of the unknown property * @param reader the ObjectReader to read property value from * @param ctx deserialization context * - * @return the optional Consumer that will be called once the target object is known + * @return the Consumer that will be called with the target bean, + * once the target bean is known */ - Consumer onUnknownProperty(T target, String propName, ObjectReader reader, Context ctx); + Consumer readUnknownProperty(String propName, ObjectReader reader, Context ctx); /** * Write unknown properties encountered during deserialization. @@ -45,12 +45,10 @@ public interface UnknownPropertyHandler { * that want to write unknown properties during serialization. The default * implementation is a no-op. * - * @param source the object we are serializing into JSON + * @param bean the object we are serializing into JSON * @param writer the ObjectReader to read property value from * @param ctx serialization context - * - * @return a map of unknown properties */ - default void writeUnknownProperties(T source, ObjectWriter writer, Context ctx) { + default void writeUnknownProperties(T bean, ObjectWriter writer, Context ctx) { } } From 2e05077b576dfa2fe9cd48249428a180137eab38 Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Thu, 31 May 2018 04:01:28 -0400 Subject: [PATCH 4/6] Switched to JsonBindingException --- .../main/java/com/owlike/genson/JsonBindingException.java | 4 ++++ .../java/com/owlike/genson/reflect/EvolvableHandler.java | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/genson/src/main/java/com/owlike/genson/JsonBindingException.java b/genson/src/main/java/com/owlike/genson/JsonBindingException.java index 6d5f78c5..15efc83b 100644 --- a/genson/src/main/java/com/owlike/genson/JsonBindingException.java +++ b/genson/src/main/java/com/owlike/genson/JsonBindingException.java @@ -6,6 +6,10 @@ public JsonBindingException(String message) { super(message); } + public JsonBindingException(Throwable cause) { + super(cause); + } + public JsonBindingException(String message, Throwable cause) { super(message, cause); } diff --git a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java index c344af8e..5ca129bd 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java +++ b/genson/src/main/java/com/owlike/genson/reflect/EvolvableHandler.java @@ -2,6 +2,7 @@ import com.owlike.genson.Context; import com.owlike.genson.Converter; +import com.owlike.genson.JsonBindingException; import com.owlike.genson.ext.jsr353.JSR353Bundle; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; @@ -40,7 +41,7 @@ public Consumer readUnknownProperty(String propName, ObjectReader reader, } }; } catch (Exception e) { - throw new RuntimeException(e); + throw new JsonBindingException(e); } } @@ -57,7 +58,7 @@ public void writeUnknownProperties(T bean, ObjectWriter writer, Context ctx) } } } catch (Exception e) { - throw new RuntimeException(e); + throw new JsonBindingException(e); } } } \ No newline at end of file From 5c6eea546d746568721202edcfb396c009ee747a Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Wed, 6 Jun 2018 14:20:34 -0400 Subject: [PATCH 5/6] Made Evolvable public --- genson/src/main/java/com/owlike/genson/reflect/Evolvable.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java index 83f03dd7..8dd81480 100644 --- a/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java +++ b/genson/src/main/java/com/owlike/genson/reflect/Evolvable.java @@ -13,7 +13,7 @@ * * @author Aleksandar Seovic 2018.05.20 */ -interface Evolvable { +public interface Evolvable { /** * Add unknown property to this instance. * From f334ab27fdd87e50efd3cef67c67be0b53d16a8a Mon Sep 17 00:00:00 2001 From: Aleksandar Seovic Date: Wed, 6 Jun 2018 15:09:51 -0400 Subject: [PATCH 6/6] Made Evolvable public --- .../owlike/genson/convert/ClassMetadataConverter.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java index ede43ad8..f29ff9e9 100644 --- a/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java +++ b/genson/src/main/java/com/owlike/genson/convert/ClassMetadataConverter.java @@ -4,12 +4,13 @@ import com.owlike.genson.*; import com.owlike.genson.annotation.HandleClassMetadata; -import com.owlike.genson.ext.jsr353.JSR353Bundle; import com.owlike.genson.reflect.TypeUtil; import com.owlike.genson.stream.ObjectReader; import com.owlike.genson.stream.ObjectWriter; import com.owlike.genson.stream.ValueType; +import javax.json.JsonValue; + /** * Converter responsible of writing and reading @class metadata. This is useful if you want to be * able to deserialize all serialized objects without knowing their concrete type. Metadata is @@ -73,7 +74,7 @@ public ClassMetadataConverter(Class tClass, Converter delegate, boolean cl } public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception { - if (obj != null && !isJsonValueConverter(wrapped) && + if (obj != null && !isJsonValue(obj.getClass()) && (classMetadataWithStaticType || !tClass.equals(obj.getClass()))) { writer.beginNextObjectMetadata() .writeMetadata("class", ctx.genson.aliasFor(obj.getClass())); @@ -82,7 +83,7 @@ public void serialize(T obj, ObjectWriter writer, Context ctx) throws Exception } public T deserialize(ObjectReader reader, Context ctx) throws Exception { - if (ValueType.OBJECT.equals(reader.getValueType()) && !isJsonValueConverter(wrapped)) { + if (ValueType.OBJECT.equals(reader.getValueType()) && !isJsonValue(tClass)) { String className = reader.nextObjectMetadata().metadata("class"); if (className != null) { try { @@ -100,7 +101,7 @@ public T deserialize(ObjectReader reader, Context ctx) throws Exception { return wrapped.deserialize(reader, ctx); } - private boolean isJsonValueConverter(Converter converter) { - return converter instanceof JSR353Bundle.JsonValueConverter; + private boolean isJsonValue(Class clazz) { + return JsonValue.class.isAssignableFrom(clazz); } }