diff --git a/zson/src/main/java/com/zeoflow/zson/Zson.java b/zson/src/main/java/com/zeoflow/zson/Zson.java index 6105f04..f40ad1e 100644 --- a/zson/src/main/java/com/zeoflow/zson/Zson.java +++ b/zson/src/main/java/com/zeoflow/zson/Zson.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 ZeoFlow + * Copyright (C) 2021 ZeoFlow * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -110,33 +110,18 @@ public class Zson static final boolean DEFAULT_PRETTY_PRINT = false; static final boolean DEFAULT_ESCAPE_HTML = true; static final boolean DEFAULT_SERIALIZE_NULLS = false; + static final boolean DEFAULT_DESERIALIZE_NULLS = true; static final boolean DEFAULT_COMPLEX_MAP_KEYS = false; static final boolean DEFAULT_SPECIALIZE_FLOAT_VALUES = false; private static final TypeToken NULL_KEY_SURROGATE = TypeToken.get(Object.class); private static final String JSON_NON_EXECUTABLE_PREFIX = ")]}'\n"; - - /** - * This thread local guards against reentrant calls to getAdapter(). In - * certain object graphs, creating an adapter for a type may recursively - * require an adapter for the same type! Without intervention, the recursive - * lookup would stack overflow. We cheat by returning a proxy type adapter. - * The proxy is wired up once the initial adapter has been created. - */ - private final ThreadLocal, FutureTypeAdapter>> calls - = new ThreadLocal, FutureTypeAdapter>>(); - - private final Map, TypeAdapter> typeTokenCache = new ConcurrentHashMap, TypeAdapter>(); - - private final ConstructorConstructor constructorConstructor; - private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; - final List factories; - final Excluder excluder; final FieldNamingStrategy fieldNamingStrategy; final Map> instanceCreators; final boolean serializeNulls; + final boolean deserializeNulls; final boolean complexMapKeySerialization; final boolean generateNonExecutableJson; final boolean htmlSafe; @@ -149,6 +134,18 @@ public class Zson final LongSerializationPolicy longSerializationPolicy; final List builderFactories; final List builderHierarchyFactories; + /** + * This thread local guards against reentrant calls to getAdapter(). In + * certain object graphs, creating an adapter for a type may recursively + * require an adapter for the same type! Without intervention, the recursive + * lookup would stack overflow. We cheat by returning a proxy type adapter. + * The proxy is wired up once the initial adapter has been created. + */ + private final ThreadLocal, FutureTypeAdapter>> calls + = new ThreadLocal, FutureTypeAdapter>>(); + private final Map, TypeAdapter> typeTokenCache = new ConcurrentHashMap, TypeAdapter>(); + private final ConstructorConstructor constructorConstructor; + private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; /** * Constructs a Zson object with default configuration. The default configuration has the @@ -187,16 +184,16 @@ public class Zson public Zson() { this(Excluder.DEFAULT, FieldNamingPolicy.IDENTITY, - Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS, - DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, - DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, - LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, - Collections.emptyList(), Collections.emptyList(), - Collections.emptyList()); + Collections.>emptyMap(), DEFAULT_SERIALIZE_NULLS, DEFAULT_DESERIALIZE_NULLS, + DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML, + DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES, + LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT, DateFormat.DEFAULT, + Collections.emptyList(), Collections.emptyList(), + Collections.emptyList()); } Zson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy, - Map> instanceCreators, boolean serializeNulls, + Map> instanceCreators, boolean serializeNulls, boolean deserializeNulls, boolean complexMapKeySerialization, boolean generateNonExecutableZson, boolean htmlSafe, boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues, LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle, @@ -209,6 +206,7 @@ public Zson() this.instanceCreators = instanceCreators; this.constructorConstructor = new ConstructorConstructor(instanceCreators); this.serializeNulls = serializeNulls; + this.deserializeNulls = deserializeNulls; this.complexMapKeySerialization = complexMapKeySerialization; this.generateNonExecutableJson = generateNonExecutableZson; this.htmlSafe = htmlSafe; @@ -243,9 +241,9 @@ public Zson() TypeAdapter longAdapter = longAdapter(longSerializationPolicy); factories.add(TypeAdapters.newFactory(long.class, Long.class, longAdapter)); factories.add(TypeAdapters.newFactory(double.class, Double.class, - doubleAdapter(serializeSpecialFloatingPointValues))); + doubleAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.newFactory(float.class, Float.class, - floatAdapter(serializeSpecialFloatingPointValues))); + floatAdapter(serializeSpecialFloatingPointValues))); factories.add(TypeAdapters.NUMBER_FACTORY); factories.add(TypeAdapters.ATOMIC_INTEGER_FACTORY); factories.add(TypeAdapters.ATOMIC_BOOLEAN_FACTORY); @@ -279,120 +277,24 @@ public Zson() factories.add(jsonAdapterFactory); factories.add(TypeAdapters.ENUM_FACTORY); factories.add(new ReflectiveTypeAdapterFactory( - constructorConstructor, fieldNamingStrategy, excluder, jsonAdapterFactory)); + constructorConstructor, + fieldNamingStrategy, + excluder, + jsonAdapterFactory, + deserializeNulls + )); this.factories = Collections.unmodifiableList(factories); } - - /** - * Returns a new ZsonBuilder containing all custom factories and configuration used by the current - * instance. - * - * @return a ZsonBuilder instance. - */ - public ZsonBuilder newBuilder() - { - return new ZsonBuilder(this); - } - - public Excluder excluder() - { - return excluder; - } - - public FieldNamingStrategy fieldNamingStrategy() - { - return fieldNamingStrategy; - } - - public boolean serializeNulls() - { - return serializeNulls; - } - - public boolean htmlSafe() - { - return htmlSafe; - } - - private TypeAdapter doubleAdapter(boolean serializeSpecialFloatingPointValues) - { - if (serializeSpecialFloatingPointValues) - { - return TypeAdapters.DOUBLE; - } - return new TypeAdapter() - { - @Override - public Double read(JsonReader in) throws IOException - { - if (in.peek() == JsonToken.NULL) - { - in.nextNull(); - return null; - } - return in.nextDouble(); - } - - @Override - public void write(JsonWriter out, Number value) throws IOException - { - if (value == null) - { - out.nullValue(); - return; - } - double doubleValue = value.doubleValue(); - checkValidFloatingPoint(doubleValue); - out.value(value); - } - }; - } - - private TypeAdapter floatAdapter(boolean serializeSpecialFloatingPointValues) - { - if (serializeSpecialFloatingPointValues) - { - return TypeAdapters.FLOAT; - } - return new TypeAdapter() - { - @Override - public Float read(JsonReader in) throws IOException - { - if (in.peek() == JsonToken.NULL) - { - in.nextNull(); - return null; - } - return (float) in.nextDouble(); - } - - @Override - public void write(JsonWriter out, Number value) throws IOException - { - if (value == null) - { - out.nullValue(); - return; - } - float floatValue = value.floatValue(); - checkValidFloatingPoint(floatValue); - out.value(value); - } - }; - } - static void checkValidFloatingPoint(double value) { if (Double.isNaN(value) || Double.isInfinite(value)) { throw new IllegalArgumentException(value - + " is not a valid double value as per JSON specification. To override this" - + " behavior, use ZsonBuilder.serializeSpecialFloatingPointValues() method."); + + " is not a valid double value as per JSON specification. To override this" + + " behavior, use ZsonBuilder.serializeSpecialFloatingPointValues() method."); } } - private static TypeAdapter longAdapter(LongSerializationPolicy longSerializationPolicy) { if (longSerializationPolicy == LongSerializationPolicy.DEFAULT) @@ -424,7 +326,6 @@ public void write(JsonWriter out, Number value) throws IOException } }; } - private static TypeAdapter atomicLongAdapter(final TypeAdapter longAdapter) { return new TypeAdapter() @@ -443,7 +344,6 @@ public AtomicLong read(JsonReader in) throws IOException } }.nullSafe(); } - private static TypeAdapter atomicLongArrayAdapter(final TypeAdapter longAdapter) { return new TypeAdapter() @@ -480,7 +380,114 @@ public AtomicLongArray read(JsonReader in) throws IOException } }.nullSafe(); } + private static void assertFullConsumption(Object obj, JsonReader reader) + { + try + { + if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) + { + throw new JsonIOException("JSON document was not fully consumed."); + } + } catch (MalformedJsonException e) + { + throw new JsonSyntaxException(e); + } catch (IOException e) + { + throw new JsonIOException(e); + } + } + /** + * Returns a new ZsonBuilder containing all custom factories and configuration used by the current + * instance. + * + * @return a ZsonBuilder instance. + */ + public ZsonBuilder newBuilder() + { + return new ZsonBuilder(this); + } + public Excluder excluder() + { + return excluder; + } + public FieldNamingStrategy fieldNamingStrategy() + { + return fieldNamingStrategy; + } + public boolean serializeNulls() + { + return serializeNulls; + } + public boolean htmlSafe() + { + return htmlSafe; + } + private TypeAdapter doubleAdapter(boolean serializeSpecialFloatingPointValues) + { + if (serializeSpecialFloatingPointValues) + { + return TypeAdapters.DOUBLE; + } + return new TypeAdapter() + { + @Override + public Double read(JsonReader in) throws IOException + { + if (in.peek() == JsonToken.NULL) + { + in.nextNull(); + return null; + } + return in.nextDouble(); + } + @Override + public void write(JsonWriter out, Number value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + double doubleValue = value.doubleValue(); + checkValidFloatingPoint(doubleValue); + out.value(value); + } + }; + } + private TypeAdapter floatAdapter(boolean serializeSpecialFloatingPointValues) + { + if (serializeSpecialFloatingPointValues) + { + return TypeAdapters.FLOAT; + } + return new TypeAdapter() + { + @Override + public Float read(JsonReader in) throws IOException + { + if (in.peek() == JsonToken.NULL) + { + in.nextNull(); + return null; + } + return (float) in.nextDouble(); + } + + @Override + public void write(JsonWriter out, Number value) throws IOException + { + if (value == null) + { + out.nullValue(); + return; + } + float floatValue = value.floatValue(); + checkValidFloatingPoint(floatValue); + out.value(value); + } + }; + } /** * Returns the type adapter for {@code} type. * @@ -538,7 +545,6 @@ public TypeAdapter getAdapter(TypeToken type) } } } - /** * This method is used to get an alternate type adapter for the specified type. This is used * to access a type adapter that is overridden by a {@link TypeAdapterFactory} that you @@ -587,6 +593,7 @@ public TypeAdapter getAdapter(TypeToken type) * a matching type adapter. In most cases, you should just pass this (the type adapter * factory from where {@link #getDelegateAdapter} method is being invoked). * @param type Type for which the delegate adapter is being searched for. + * * @since 2.2 */ public TypeAdapter getDelegateAdapter(TypeAdapterFactory skipPast, TypeToken type) @@ -618,7 +625,6 @@ public TypeAdapter getDelegateAdapter(TypeAdapterFactory skipPast, TypeTo } throw new IllegalArgumentException("Zson cannot serialize " + type); } - /** * Returns the type adapter for {@code} type. * @@ -629,7 +635,6 @@ public TypeAdapter getAdapter(Class type) { return getAdapter(TypeToken.get(type)); } - /** * This method serializes the specified object into its equivalent representation as a tree of * {@link JsonElement}s. This method should be used when the specified object is not a generic @@ -640,7 +645,9 @@ public TypeAdapter getAdapter(Class type) * {@link #toJsonTree(Object, Type)} instead. * * @param src the object for which Json representation is to be created setting for Zson + * * @return Json representation of {@code src}. + * * @since 1.4 */ public JsonElement toJsonTree(Object src) @@ -651,7 +658,6 @@ public JsonElement toJsonTree(Object src) } return toJsonTree(src, src.getClass()); } - /** * This method serializes the specified object, including those of generic types, into its * equivalent representation as a tree of {@link JsonElement}s. This method must be used if the @@ -663,9 +669,11 @@ public JsonElement toJsonTree(Object src) * this type by using the {@link TypeToken} class. For example, * to get the type for {@code Collection}, you should use: *
-     *                                                                                      Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                                      
+ * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType(); + * + * * @return Json representation of {@code src} + * * @since 1.4 */ public JsonElement toJsonTree(Object src, Type typeOfSrc) @@ -674,7 +682,6 @@ public JsonElement toJsonTree(Object src, Type typeOfSrc) toJson(src, typeOfSrc, writer); return writer.get(); } - /** * This method serializes the specified object into its equivalent Json representation. * This method should be used when the specified object is not a generic type. This method uses @@ -686,6 +693,7 @@ public JsonElement toJsonTree(Object src, Type typeOfSrc) * {@link Writer}, use {@link #toJson(Object, Appendable)} instead. * * @param src the object for which Json representation is to be created setting for Zson + * * @return Json representation of {@code src}. */ public String toJson(Object src) @@ -696,7 +704,6 @@ public String toJson(Object src) } return toJson(src, src.getClass()); } - /** * This method serializes the specified object, including those of generic types, into its * equivalent Json representation. This method must be used if the specified object is a generic @@ -708,8 +715,9 @@ public String toJson(Object src) * this type by using the {@link TypeToken} class. For example, * to get the type for {@code Collection}, you should use: *
-     *                                                                                      Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                                      
+ * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType(); + * + * * @return Json representation of {@code src} */ public String toJson(Object src, Type typeOfSrc) @@ -718,7 +726,6 @@ public String toJson(Object src, Type typeOfSrc) toJson(src, typeOfSrc, writer); return writer.toString(); } - /** * This method serializes the specified object into its equivalent Json representation. * This method should be used when the specified object is not a generic type. This method uses @@ -730,6 +737,7 @@ public String toJson(Object src, Type typeOfSrc) * * @param src the object for which Json representation is to be created setting for Zson * @param writer Writer to which the Json representation needs to be written + * * @throws JsonIOException if there was a problem writing to the writer * @since 1.2 */ @@ -743,7 +751,6 @@ public void toJson(Object src, Appendable writer) throws JsonIOException toJson(JsonNull.INSTANCE, writer); } } - /** * This method serializes the specified object, including those of generic types, into its * equivalent Json representation. This method must be used if the specified object is a generic @@ -754,9 +761,10 @@ public void toJson(Object src, Appendable writer) throws JsonIOException * this type by using the {@link TypeToken} class. For example, * to get the type for {@code Collection}, you should use: *
-     *                                                                                      Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                                      
+ * Type typeOfSrc = new TypeToken<Collection<Foo>>(){}.getType(); + * * @param writer Writer to which the Json representation of src needs to be written. + * * @throws JsonIOException if there was a problem writing to the writer * @since 1.2 */ @@ -771,7 +779,6 @@ public void toJson(Object src, Type typeOfSrc, Appendable writer) throws JsonIOE throw new JsonIOException(e); } } - /** * Writes the JSON representation of {@code src} of type {@code typeOfSrc} to * {@code writer}. @@ -806,12 +813,13 @@ public void toJson(Object src, Type typeOfSrc, JsonWriter writer) throws JsonIOE writer.setSerializeNulls(oldSerializeNulls); } } - /** * Converts a tree of {@link JsonElement}s into its equivalent JSON representation. * * @param jsonElement root of a tree of {@link JsonElement}s + * * @return JSON String representation of the tree + * * @since 1.4 */ public String toJson(JsonElement jsonElement) @@ -820,12 +828,12 @@ public String toJson(JsonElement jsonElement) toJson(jsonElement, writer); return writer.toString(); } - /** * Writes out the equivalent JSON for a tree of {@link JsonElement}s. * * @param jsonElement root of a tree of {@link JsonElement}s * @param writer Writer to which the Json representation needs to be written + * * @throws JsonIOException if there was a problem writing to the writer * @since 1.4 */ @@ -840,7 +848,6 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce throw new JsonIOException(e); } } - /** * Returns a new JSON writer configured for the settings on this Zson instance. */ @@ -858,7 +865,6 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException jsonWriter.setSerializeNulls(serializeNulls); return jsonWriter; } - /** * Returns a new JSON reader configured for the settings on this Zson instance. */ @@ -868,7 +874,6 @@ public JsonReader newJsonReader(Reader reader) jsonReader.setLenient(lenient); return jsonReader; } - /** * Writes the JSON for {@code jsonElement} to {@code writer}. * @@ -900,7 +905,6 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce writer.setSerializeNulls(oldSerializeNulls); } } - /** * This method deserializes the specified Json into an object of the specified class. It is not * suitable to use if the specified class is a generic type since it will not have the generic @@ -914,8 +918,10 @@ public void toJson(JsonElement jsonElement, JsonWriter writer) throws JsonIOExce * @param the type of the desired object * @param json the string from which the object is to be deserialized * @param classOfT the class of T + * * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. + * * @throws JsonSyntaxException if json is not a valid representation for an object of type * classOfT */ @@ -924,7 +930,6 @@ public T fromJson(String json, Class classOfT) throws JsonSyntaxException Object object = fromJson(json, (Type) classOfT); return Primitives.wrap(classOfT).cast(object); } - /** * This method deserializes the specified Json into an object of the specified type. This method * is useful if the specified object is a generic type. For non-generic objects, use @@ -937,10 +942,12 @@ public T fromJson(String json, Class classOfT) throws JsonSyntaxException * {@link TypeToken} class. For example, to get the type for * {@code Collection}, you should use: *
-     *                                                                            Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                            
+ * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType(); + * + * * @return an object of type T from the string. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. + * * @throws JsonParseException if json is not a valid representation for an object of type typeOfT * @throws JsonSyntaxException if json is not a valid representation for an object of type */ @@ -954,7 +961,6 @@ public T fromJson(String json, Type typeOfT) throws JsonSyntaxException StringReader reader = new StringReader(json); return (T) fromJson(reader, typeOfT); } - /** * This method deserializes the Json read from the specified reader into an object of the * specified class. It is not suitable to use if the specified class is a generic type since it @@ -968,7 +974,9 @@ public T fromJson(String json, Type typeOfT) throws JsonSyntaxException * @param the type of the desired object * @param json the reader producing the Json from which the object is to be deserialized. * @param classOfT the class of T + * * @return an object of type T from the string. Returns {@code null} if {@code json} is at EOF. + * * @throws JsonIOException if there was a problem reading from the Reader * @throws JsonSyntaxException if json is not a valid representation for an object of type * @since 1.2 @@ -980,7 +988,6 @@ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException assertFullConsumption(object, jsonReader); return Primitives.wrap(classOfT).cast(object); } - /** * This method deserializes the Json read from the specified reader into an object of the * specified type. This method is useful if the specified object is a generic type. For @@ -993,9 +1000,11 @@ public T fromJson(Reader json, Class classOfT) throws JsonSyntaxException * {@link TypeToken} class. For example, to get the type for * {@code Collection}, you should use: *
-     *                                                                            Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                            
+ * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType(); + * + * * @return an object of type T from the json. Returns {@code null} if {@code json} is at EOF. + * * @throws JsonIOException if there was a problem reading from the Reader * @throws JsonSyntaxException if json is not a valid representation for an object of type * @since 1.2 @@ -1008,24 +1017,6 @@ public T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyn assertFullConsumption(object, jsonReader); return object; } - - private static void assertFullConsumption(Object obj, JsonReader reader) - { - try - { - if (obj != null && reader.peek() != JsonToken.END_DOCUMENT) - { - throw new JsonIOException("JSON document was not fully consumed."); - } - } catch (MalformedJsonException e) - { - throw new JsonSyntaxException(e); - } catch (IOException e) - { - throw new JsonIOException(e); - } - } - /** * Reads the next JSON value from {@code reader} and convert it to an object * of type {@code typeOfT}. Returns {@code null}, if the {@code reader} is at EOF. @@ -1090,8 +1081,10 @@ public T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, J * @param json the root of the parse tree of {@link JsonElement}s from which the object is to * be deserialized * @param classOfT The class of T + * * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. + * * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT * @since 1.3 */ @@ -1113,10 +1106,12 @@ public T fromJson(JsonElement json, Class classOfT) throws JsonSyntaxExce * {@link TypeToken} class. For example, to get the type for * {@code Collection}, you should use: *
-     *                                                                            Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType();
-     *                                                                            
+ * Type typeOfT = new TypeToken<Collection<Foo>>(){}.getType(); + * + * * @return an object of type T from the json. Returns {@code null} if {@code json} is {@code null} * or if {@code json} is empty. + * * @throws JsonSyntaxException if json is not a valid representation for an object of type typeOfT * @since 1.3 */ @@ -1129,9 +1124,20 @@ public T fromJson(JsonElement json, Type typeOfT) throws JsonSyntaxException } return (T) fromJson(new JsonTreeReader(json), typeOfT); } + @Override + public String toString() + { + return new StringBuilder("{serializeNulls:") + .append(serializeNulls) + .append(",factories:").append(factories) + .append(",instanceCreators:").append(constructorConstructor) + .append("}") + .toString(); + } static class FutureTypeAdapter extends TypeAdapter { + private TypeAdapter delegate; public void setDelegate(TypeAdapter typeAdapter) @@ -1162,16 +1168,7 @@ public void write(JsonWriter out, T value) throws IOException } delegate.write(out, value); } - } - @Override - public String toString() - { - return new StringBuilder("{serializeNulls:") - .append(serializeNulls) - .append(",factories:").append(factories) - .append(",instanceCreators:").append(constructorConstructor) - .append("}") - .toString(); } + } diff --git a/zson/src/main/java/com/zeoflow/zson/ZsonBuilder.java b/zson/src/main/java/com/zeoflow/zson/ZsonBuilder.java index 1aa3b66..e9bbc55 100644 --- a/zson/src/main/java/com/zeoflow/zson/ZsonBuilder.java +++ b/zson/src/main/java/com/zeoflow/zson/ZsonBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 ZeoFlow + * Copyright (C) 2021 ZeoFlow * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import com.zeoflow.zson.annotations.Expose; import static com.zeoflow.zson.Zson.DEFAULT_COMPLEX_MAP_KEYS; +import static com.zeoflow.zson.Zson.DEFAULT_DESERIALIZE_NULLS; import static com.zeoflow.zson.Zson.DEFAULT_ESCAPE_HTML; import static com.zeoflow.zson.Zson.DEFAULT_JSON_NON_EXECUTABLE; import static com.zeoflow.zson.Zson.DEFAULT_LENIENT; @@ -91,6 +92,7 @@ public final class ZsonBuilder private int timeStyle = DateFormat.DEFAULT; private boolean complexMapKeySerialization = DEFAULT_COMPLEX_MAP_KEYS; private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES; + private boolean deserializeNulls = DEFAULT_DESERIALIZE_NULLS; private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML; private boolean prettyPrinting = DEFAULT_PRETTY_PRINT; private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE; @@ -116,6 +118,7 @@ public ZsonBuilder() { this.fieldNamingPolicy = zson.fieldNamingStrategy; this.instanceCreators.putAll(zson.instanceCreators); this.serializeNulls = zson.serializeNulls; + this.deserializeNulls = zson.deserializeNulls; this.complexMapKeySerialization = zson.complexMapKeySerialization; this.generateNonExecutableJson = zson.generateNonExecutableJson; this.escapeHtmlChars = zson.htmlSafe; @@ -195,6 +198,18 @@ public ZsonBuilder serializeNulls() { return this; } + /** + * Configure Zson to deserialize null fields. By default, Zson don't omits all fields that are null + * during deserialization. + * + * @return a reference to this {@code ZsonBuilder} object to fulfill the "Builder" pattern + * @since 2.8 + */ + public ZsonBuilder deserializeNulls(boolean deserialize) { + this.deserializeNulls = deserialize; + return this; + } + /** * Enabling this feature will only change the serialized form if the map key is * a complex type (i.e. non-primitive) in its serialized JSON @@ -596,7 +611,7 @@ public Zson create() { addTypeAdaptersForDate(datePattern, dateStyle, timeStyle, factories); return new Zson(excluder, fieldNamingPolicy, instanceCreators, - serializeNulls, complexMapKeySerialization, + serializeNulls, deserializeNulls, complexMapKeySerialization, generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient, serializeSpecialFloatingPointValues, longSerializationPolicy, datePattern, dateStyle, timeStyle, diff --git a/zson/src/main/java/com/zeoflow/zson/internal/bind/ReflectiveTypeAdapterFactory.java b/zson/src/main/java/com/zeoflow/zson/internal/bind/ReflectiveTypeAdapterFactory.java index b633ebe..e12ea68 100644 --- a/zson/src/main/java/com/zeoflow/zson/internal/bind/ReflectiveTypeAdapterFactory.java +++ b/zson/src/main/java/com/zeoflow/zson/internal/bind/ReflectiveTypeAdapterFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 ZeoFlow + * Copyright (C) 2021 ZeoFlow * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,14 +52,16 @@ public final class ReflectiveTypeAdapterFactory implements TypeAdapterFactory { private final Excluder excluder; private final JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory; private final ReflectionAccessor accessor = ReflectionAccessor.getInstance(); + private final boolean deserializeNulls; public ReflectiveTypeAdapterFactory(ConstructorConstructor constructorConstructor, FieldNamingStrategy fieldNamingPolicy, Excluder excluder, - JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory) { + JsonAdapterAnnotationTypeAdapterFactory jsonAdapterFactory, boolean deserializeNulls) { this.constructorConstructor = constructorConstructor; this.fieldNamingPolicy = fieldNamingPolicy; this.excluder = excluder; this.jsonAdapterFactory = jsonAdapterFactory; + this.deserializeNulls = deserializeNulls; } public boolean excludeField(Field f, boolean serialize) { @@ -130,6 +132,9 @@ private ReflectiveTypeAdapterFactory.BoundField createBoundField( @Override void read(JsonReader reader, Object value) throws IOException, IllegalAccessException { Object fieldValue = typeAdapter.read(reader); + if (fieldValue == null && !deserializeNulls){ + return; + } if (fieldValue != null || !isPrimitive) { field.set(value, fieldValue); }