From b90228dcc096c88b176e53f93b6069f29abccc5a Mon Sep 17 00:00:00 2001 From: Joel Leitch Date: Sun, 30 Nov 2008 23:01:14 +0000 Subject: [PATCH] Enable serialization of nulls within Maps. --- .../com/google/gson/DefaultTypeAdapters.java | 21 +++++--- .../gson/JsonArrayDeserializationVisitor.java | 2 +- .../main/java/com/google/gson/JsonNull.java | 22 +++++++-- .../main/java/com/google/gson/JsonObject.java | 2 +- .../JsonObjectDeserializationVisitor.java | 2 +- .../main/java/com/google/gson/JsonParser.java | 4 +- .../google/gson/JsonSerializationVisitor.java | 8 +-- .../com/google/gson/JsonTreeNavigator.java | 2 +- gson/src/main/javacc/JsonParser.jj | 4 +- .../com/google/gson/functional/MapTest.java | 49 +++++++++++++++++++ 10 files changed, 93 insertions(+), 23 deletions(-) diff --git a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java index 17ba400b..98800482 100644 --- a/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java +++ b/gson/src/main/java/com/google/gson/DefaultTypeAdapters.java @@ -31,7 +31,6 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Date; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; @@ -374,7 +373,7 @@ final class DefaultTypeAdapters { public JsonElement serialize(Collection src, Type typeOfSrc, JsonSerializationContext context) { if (src == null) { - return JsonNull.INSTANCE; + return JsonNull.createJsonNull(); } JsonArray array = new JsonArray(); Type childGenericType = null; @@ -432,13 +431,19 @@ final class DefaultTypeAdapters { if (typeOfSrc instanceof ParameterizedType) { childGenericType = new TypeInfoMap(typeOfSrc).getValueType(); } - for (Iterator iterator = src.entrySet().iterator(); iterator.hasNext(); ) { - Map.Entry entry = (Map.Entry) iterator.next(); + + for (Map.Entry entry : (Set) src.entrySet()) { Object value = entry.getValue(); - Type childType = (childGenericType == null) ? - childType = value.getClass() : childGenericType; - JsonElement valueElement = context.serialize(value, childType); - map.add(entry.getKey().toString(), valueElement); + + JsonElement valueElement; + if (value == null) { + valueElement = JsonNull.createJsonNull(); + } else { + Type childType = (childGenericType == null) ? + childType = value.getClass() : childGenericType; + valueElement = context.serialize(value, childType); + } + map.add(String.valueOf(entry.getKey()), valueElement); } return map; } diff --git a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java index f166fced..835a64bc 100644 --- a/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonArrayDeserializationVisitor.java @@ -61,7 +61,7 @@ final class JsonArrayDeserializationVisitor extends JsonDeserializationVisito JsonElement jsonChild = jsonArray.get(i); Object child; - if (jsonChild == null) { + if (jsonChild == null || jsonChild.isJsonNull()) { child = null; } else if (jsonChild instanceof JsonObject) { child = visitChildAsObject(arrayTypeInfo.getComponentRawType(), jsonChild); diff --git a/gson/src/main/java/com/google/gson/JsonNull.java b/gson/src/main/java/com/google/gson/JsonNull.java index 6cc99c0e..80293abb 100755 --- a/gson/src/main/java/com/google/gson/JsonNull.java +++ b/gson/src/main/java/com/google/gson/JsonNull.java @@ -17,7 +17,7 @@ package com.google.gson; /** - * A class representing a Json null value. + * A class representing a Json {@code null} value. * * @author Inderjeet Singh * @author Joel Leitch @@ -25,7 +25,19 @@ package com.google.gson; */ public final class JsonNull extends JsonElement { - static final JsonNull INSTANCE = new JsonNull(); + private static final JsonNull INSTANCE = new JsonNull(true); + + /** + * @deprecated use the creation method, {@link #createJsonNull()}, instead. + */ + @Deprecated + public JsonNull() { + // Do nothing + } + + private JsonNull(boolean placeholder) { + // Prevent instantiation + } @Override protected void toString(StringBuilder sb) { @@ -46,5 +58,9 @@ public final class JsonNull extends JsonElement { @Override public boolean equals(Object other) { return other instanceof JsonNull; - } + } + + public static JsonNull createJsonNull() { + return INSTANCE; + } } diff --git a/gson/src/main/java/com/google/gson/JsonObject.java b/gson/src/main/java/com/google/gson/JsonObject.java index 1775ede8..3c9a55b0 100644 --- a/gson/src/main/java/com/google/gson/JsonObject.java +++ b/gson/src/main/java/com/google/gson/JsonObject.java @@ -115,7 +115,7 @@ public final class JsonObject extends JsonElement { public JsonElement get(String memberName) { if (members.containsKey(memberName)) { JsonElement member = members.get(memberName); - return member == null ? JsonNull.INSTANCE : member; + return member == null ? JsonNull.createJsonNull() : member; } else { return null; } diff --git a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java index 01965348..77e681d2 100644 --- a/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonObjectDeserializationVisitor.java @@ -93,7 +93,7 @@ final class JsonObjectDeserializationVisitor extends JsonDeserializationVisit JsonElement child = json.getAsJsonObject().get(fName); if (child == null) { return true; - } else if (JsonNull.INSTANCE.equals(child)) { + } else if (child.isJsonNull()) { TypeInfo typeInfo = new TypeInfo(actualTypeOfField); if (!typeInfo.isPrimitive()) { f.set(parent, null); diff --git a/gson/src/main/java/com/google/gson/JsonParser.java b/gson/src/main/java/com/google/gson/JsonParser.java index 84403950..95540453 100755 --- a/gson/src/main/java/com/google/gson/JsonParser.java +++ b/gson/src/main/java/com/google/gson/JsonParser.java @@ -53,7 +53,7 @@ final class JsonParser implements JsonParserConstants { final private JsonNull JsonNull() throws ParseException { Token t; t = jj_consume_token(NULL); - {if (true) return JsonNull.INSTANCE;} + {if (true) return JsonNull.createJsonNull();} throw new Error("Missing return statement in function"); } @@ -159,7 +159,7 @@ final class JsonParser implements JsonParserConstants { o = JsonBoolean(); break; case NULL: - jj_consume_token(NULL); + o = JsonNull(); break; default: jj_la1[6] = jj_gen; diff --git a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java index afa0c27c..0dfebcac 100644 --- a/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java +++ b/gson/src/main/java/com/google/gson/JsonSerializationVisitor.java @@ -67,7 +67,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { public void visitArrayField(Field f, Type typeOfF, Object obj) { if (isFieldNull(f, obj)) { if (serializeNulls) { - addChildAsElement(f, JsonNull.INSTANCE); + addChildAsElement(f, JsonNull.createJsonNull()); } } else { Object array = getFieldValue(f, obj); @@ -78,7 +78,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { public void visitObjectField(Field f, Type typeOfF, Object obj) { if (isFieldNull(f, obj)) { if (serializeNulls) { - addChildAsElement(f, JsonNull.INSTANCE); + addChildAsElement(f, JsonNull.createJsonNull()); } } else { Object fieldValue = getFieldValue(f, obj); @@ -127,7 +127,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { JsonSerializer serializer = serializers.getHandlerFor(objType); if (serializer != null) { if (obj == null) { - assignToRoot(JsonNull.INSTANCE); + assignToRoot(JsonNull.createJsonNull()); } else { assignToRoot(serializer.serialize(obj, objType, context)); } @@ -143,7 +143,7 @@ final class JsonSerializationVisitor implements ObjectNavigator.Visitor { Object obj = f.get(parent); if (obj == null) { if (serializeNulls) { - addChildAsElement(f, JsonNull.INSTANCE); + addChildAsElement(f, JsonNull.createJsonNull()); } return true; } diff --git a/gson/src/main/java/com/google/gson/JsonTreeNavigator.java b/gson/src/main/java/com/google/gson/JsonTreeNavigator.java index 88d5977e..36d338bc 100644 --- a/gson/src/main/java/com/google/gson/JsonTreeNavigator.java +++ b/gson/src/main/java/com/google/gson/JsonTreeNavigator.java @@ -47,7 +47,7 @@ final class JsonTreeNavigator { } } visitor.endArray(array); - } else if (element.isJsonObject()){ + } else if (element.isJsonObject()) { JsonObject object = element.getAsJsonObject(); visitor.startObject(object); boolean isFirst = true; diff --git a/gson/src/main/javacc/JsonParser.jj b/gson/src/main/javacc/JsonParser.jj index c4b2ef71..c5c349cc 100755 --- a/gson/src/main/javacc/JsonParser.jj +++ b/gson/src/main/javacc/JsonParser.jj @@ -78,7 +78,7 @@ private JsonNull JsonNull() : Token t; } { - t = { return JsonNull.INSTANCE; } + t = { return JsonNull.createJsonNull(); } } private void Members(JsonObject o) : @@ -133,7 +133,7 @@ private JsonElement JsonValue() : o=JsonObject() | o=JsonArray() | o=JsonBoolean() | - "null" ) + o=JsonNull() ) { return o; } } diff --git a/gson/src/test/java/com/google/gson/functional/MapTest.java b/gson/src/test/java/com/google/gson/functional/MapTest.java index aee92b6a..9d7bb0ab 100755 --- a/gson/src/test/java/com/google/gson/functional/MapTest.java +++ b/gson/src/test/java/com/google/gson/functional/MapTest.java @@ -75,6 +75,55 @@ public class MapTest extends TestCase { String json = gson.toJson(map, typeOfMap); assertEquals("{}", json); } + + public void testMapDeserializationEmpty() { + Type typeOfMap = new TypeToken>() {}.getType(); + Map map = gson.fromJson("{}", typeOfMap); + assertTrue(map.isEmpty()); + } + + public void testMapSerializationWithNullValue() { + Map map = new LinkedHashMap(); + map.put("abc", null); + Type typeOfMap = new TypeToken>() {}.getType(); + String json = gson.toJson(map, typeOfMap); + + // Maps are represented as JSON objects, so ignoring null field + assertEquals("{}", json); + } + + public void testMapDeserializationWithNullValue() { + Type typeOfMap = new TypeToken>() {}.getType(); + Map map = gson.fromJson("{\"abc\":null}", typeOfMap); + assertEquals(1, map.size()); + assertNull(map.get("abc")); + } + + public void testMapSerializationWithNullValueButSerializeNulls() { + gson = new GsonBuilder().serializeNulls().create(); + Map map = new LinkedHashMap(); + map.put("abc", null); + Type typeOfMap = new TypeToken>() {}.getType(); + String json = gson.toJson(map, typeOfMap); + + assertEquals("{\"abc\":null}", json); + } + + public void testMapSerializationWithNullKey() { + Map map = new LinkedHashMap(); + map.put(null, 123); + Type typeOfMap = new TypeToken>() {}.getType(); + String json = gson.toJson(map, typeOfMap); + + assertEquals("{\"null\":123}", json); + } + + public void testMapDeserializationWithNullKey() { + Type typeOfMap = new TypeToken>() {}.getType(); + Map map = gson.fromJson("{\"null\":123}", typeOfMap); + assertEquals(1, map.size()); + assertNull(map.get(null)); + } public void testParameterizedMapSubclassSerialization() { MyParameterizedMap map = new MyParameterizedMap();