diff --git a/src/main/java/com/avaje/ebean/text/json/EJson.java b/src/main/java/com/avaje/ebean/text/json/EJson.java index 16250549a..b624cf41b 100644 --- a/src/main/java/com/avaje/ebean/text/json/EJson.java +++ b/src/main/java/com/avaje/ebean/text/json/EJson.java @@ -8,6 +8,7 @@ import java.util.Map; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; /** * Utility that converts between JSON content and simple java Maps/Lists. @@ -55,7 +56,17 @@ public class EJson { public static Map parseObject(JsonParser parser) throws IOException { return EJsonReader.parseObject(parser); } - + + /** + * Parse the json and return as a Map taking a JsonParser and a starting token. + *

+ * Used when the first token is checked to see if the value is null prior to calling this. + *

+ */ + public static Map parseObject(JsonParser parser, JsonToken token) throws IOException { + return EJsonReader.parseObject(parser, token); + } + /** * Parse the json and return as a List. * @throws IOException diff --git a/src/main/java/com/avaje/ebean/text/json/EJsonReader.java b/src/main/java/com/avaje/ebean/text/json/EJsonReader.java index c9e8283bb..c644a9266 100644 --- a/src/main/java/com/avaje/ebean/text/json/EJsonReader.java +++ b/src/main/java/com/avaje/ebean/text/json/EJsonReader.java @@ -32,6 +32,11 @@ class EJsonReader { return (Map) parse(parser); } + @SuppressWarnings("unchecked") + static Map parseObject(JsonParser parser, JsonToken token) throws IOException { + return (Map)parse(parser, token); + } + @SuppressWarnings("unchecked") static List parseList(String json) throws IOException { return (List) parse(json); @@ -56,7 +61,11 @@ class EJsonReader { } static Object parse(JsonParser parser) throws IOException { - return new EJsonReader(parser).parseJson(); + return parse(parser, null); + } + + static Object parse(JsonParser parser, JsonToken token) throws IOException { + return new EJsonReader(parser).parseJson(token); } private final JsonParser parser; @@ -106,28 +115,27 @@ class EJsonReader { currentContext.setValueNull(); } - private Object parseJson() { + private Object parseJson(JsonToken token) throws IOException { - try { - JsonToken token = parser.nextToken(); + if (token == null) { + // no initial token so expect to read START_OBJECT or similar + token = parser.nextToken(); if (JsonToken.VALUE_NULL == token) { return null; } - - stack = new Stack(); - // it is a object or array, process the first JsonToken - processJsonToken(token); - - // process the rest of the object or array - while (depth > 0) { - token = parser.nextToken(); - processJsonToken(token); - } - - return currentContext.getValue(); - } catch (IOException e) { - throw new RuntimeException(e); } + + stack = new Stack(); + // it is a object or array, process the first JsonToken + processJsonToken(token); + + // process the rest of the object or array + while (depth > 0) { + token = parser.nextToken(); + processJsonToken(token); + } + + return currentContext.getValue(); } /** diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java index d2d9f8709..847f5fc4b 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java +++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstore.java @@ -48,7 +48,7 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase { if (value == null) { return null; } - if (value instanceof Map == false) { + if (!(value instanceof Map)) { throw new RuntimeException("Expecting Hstore to return as Map but got type "+value.getClass()); } return new ModifyAwareMap((Map)value); @@ -111,12 +111,21 @@ public class ScalarTypePostgresHstore extends ScalarTypeBase { @Override public void jsonWrite(JsonGenerator ctx, String name, Object value) throws IOException { - EJson.write(value, ctx); + // write the field name followed by the Map/JSON Object + if (value == null) { + ctx.writeNullField(name); + } else { + ctx.writeFieldName(name); + EJson.write(value, ctx); + } } @Override public Object jsonRead(JsonParser ctx, JsonToken event) throws IOException { - return EJson.parseObject(ctx); + // at this point the BeanProperty has read the START_OBJECT token + // to check for a null value. Pass the START_OBJECT token through to + // the EJson parsing so that it knows the first token has been read + return EJson.parseObject(ctx, event); } } diff --git a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstoreTest.java b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstoreTest.java index b5b8cf3de..fda32cebd 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstoreTest.java +++ b/src/test/java/com/avaje/ebeaninternal/server/type/ScalarTypePostgresHstoreTest.java @@ -57,41 +57,54 @@ public class ScalarTypePostgresHstoreTest { @Test public void testJsonWrite() throws Exception { + + assertEquals("{\"key\":null}", generateJson(null)); + Map map = new LinkedHashMap(); - assertEquals("{}", generateJson(map)); + assertEquals("{\"key\":{}}", generateJson(map)); map.put("name", "rob"); - assertEquals("{\"name\":\"rob\"}", generateJson(map)); + assertEquals("{\"key\":{\"name\":\"rob\"}}", generateJson(map)); map.put("age", 12); - assertEquals("{\"name\":\"rob\",\"age\":12}", generateJson(map)); + assertEquals("{\"key\":{\"name\":\"rob\",\"age\":12}}", generateJson(map)); } @Test public void testJsonRead() throws Exception { - Map map = parse("{\"name\":\"rob\"}"); + Map map = parseHstore("{\"name\":\"rob\"}"); assertEquals(1, map.size()); assertEquals("rob", map.get("name")); - map = parse("{\"name\":\"rob\",\"age\":12}"); + map = parseHstore("{\"name\":\"rob\",\"age\":12}"); assertEquals(2, map.size()); assertEquals("rob", map.get("name")); assertEquals(12L, map.get("age")); } - private Map parse(String json) throws IOException { + private Map parseHstore(String json) throws IOException { JsonParser parser = jsonFactory.createParser(json); - return (Map)hstore.jsonRead(parser, JsonToken.FIELD_NAME); + // BeanProperty reads the first token checking for null so + // simulate that here + JsonToken token = parser.nextToken(); + assertEquals(JsonToken.START_OBJECT, token); + return (Map)hstore.jsonRead(parser, token); } - private String generateJson(Map emptyMap) throws IOException { + private String generateJson(Map map) throws IOException { StringWriter writer = new StringWriter(); JsonGenerator generator = jsonFactory.createGenerator(writer); - hstore.jsonWrite(generator, "name", emptyMap); + // wrap in an object to form proper json + generator.writeStartObject(); + + hstore.jsonWrite(generator, "key", map); + + generator.writeEndObject(); generator.flush(); + return writer.toString(); }