diff --git a/src/main/java/com/avaje/ebean/text/json/JsonContext.java b/src/main/java/com/avaje/ebean/text/json/JsonContext.java index 5d42d8949..4ef3da2e7 100644 --- a/src/main/java/com/avaje/ebean/text/json/JsonContext.java +++ b/src/main/java/com/avaje/ebean/text/json/JsonContext.java @@ -21,6 +21,13 @@ public interface JsonContext { */ T toBean(Class rootType, String json) throws JsonIOException; + /** + * Convert json string input into a Bean of a specific type additionally using JsonReadOptions. + * + * @throws JsonIOException When IOException occurs + */ + T toBean(Class rootType, String json, JsonReadOptions options) throws JsonIOException; + /** * Convert json reader input into a Bean of a specific type. * @@ -28,6 +35,13 @@ public interface JsonContext { */ T toBean(Class rootType, Reader json) throws JsonIOException; + /** + * Convert json reader input into a Bean of a specific type additionally using JsonReadOptions. + * + * @throws JsonIOException When IOException occurs + */ + T toBean(Class rootType, Reader json, JsonReadOptions options) throws JsonIOException; + /** * Convert json parser input into a Bean of a specific type. * @@ -35,6 +49,13 @@ public interface JsonContext { */ T toBean(Class cls, JsonParser parser) throws JsonIOException; + /** + * Convert json parser input into a Bean of a specific type additionally using JsonReadOptions.. + * + * @throws JsonIOException When IOException occurs + */ + T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException; + /** * Convert json string input into a list of beans of a specific type. * @@ -42,6 +63,13 @@ public interface JsonContext { */ List toList(Class rootType, String json) throws JsonIOException; + /** + * Convert json string input into a list of beans of a specific type additionally using JsonReadOptions. + * + * @throws JsonIOException When IOException occurs + */ + List toList(Class rootType, String json, JsonReadOptions options) throws JsonIOException; + /** * Convert json reader input into a list of beans of a specific type. * @@ -49,12 +77,26 @@ public interface JsonContext { */ List toList(Class rootType, Reader json) throws JsonIOException; + /** + * Convert json reader input into a list of beans of a specific type additionally using JsonReadOptions. + * + * @throws JsonIOException When IOException occurs + */ + List toList(Class rootType, Reader json, JsonReadOptions options) throws JsonIOException; + /** * Convert json parser input into a list of beans of a specific type. * * @throws JsonIOException When IOException occurs */ - List toList(Class cls, JsonParser src) throws JsonIOException; + List toList(Class cls, JsonParser json) throws JsonIOException; + + /** + * Convert json parser input into a list of beans of a specific type additionally using JsonReadOptions. + * + * @throws JsonIOException When IOException occurs + */ + List toList(Class cls, JsonParser json, JsonReadOptions options) throws JsonIOException; /** * Use the genericType to determine if this should be converted into a List or diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadBeanVisitor.java b/src/main/java/com/avaje/ebean/text/json/JsonReadBeanVisitor.java new file mode 100644 index 000000000..5d5b63dc3 --- /dev/null +++ b/src/main/java/com/avaje/ebean/text/json/JsonReadBeanVisitor.java @@ -0,0 +1,30 @@ +package com.avaje.ebean.text.json; + + +import java.util.Map; + +/** + * Provides for custom handling of json content as it is read. + *

+ * This visit method is called after all the known properties of the bean have + * been processed. Any JSON elements that could not be mapped to known bean + * properties are available in the unmapped Map. + *

+ * + * @param The type of entity bean + */ +public interface JsonReadBeanVisitor { + + /** + * Visit the bean that has just been processed. + *

+ * This provides a method of customising the bean and processing any custom + * JSON content. + *

+ * + * @param bean the bean being processed + * @param unmapped Map of any JSON elements that didn't map to known bean properties + */ + void visit(T bean, Map unmapped); + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java new file mode 100644 index 000000000..81d4efd6f --- /dev/null +++ b/src/main/java/com/avaje/ebean/text/json/JsonReadOptions.java @@ -0,0 +1,47 @@ +package com.avaje.ebean.text.json; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Provides the ability to customise the reading of JSON content. + *

+ * You can register JsonReadBeanVisitors to customise the processing of the + * beans as they are processed and handle any custom JSON elements that + * could not be mapped to bean properties. + *

+ */ +public class JsonReadOptions { + + protected Map> visitorMap; + + /** + * Default constructor. + */ + public JsonReadOptions() { + this.visitorMap = new LinkedHashMap>(); + } + + /** + * Return the map of JsonReadBeanVisitor's. + */ + public Map> getVisitorMap() { + return visitorMap; + } + + /** + * Register a JsonReadBeanVisitor for the root level. + */ + public JsonReadOptions addRootVisitor(JsonReadBeanVisitor visitor) { + return addVisitor(null, visitor); + } + + /** + * Register a JsonReadBeanVisitor for a given path. + */ + public JsonReadOptions addVisitor(String path, JsonReadBeanVisitor visitor) { + visitorMap.put(path, visitor); + return this; + } + +} \ No newline at end of file diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java index 12c9faba6..af4db3894 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptor.java @@ -19,7 +19,6 @@ import com.avaje.ebean.meta.MetaQueryPlanStatistic; import com.avaje.ebeaninternal.api.*; import com.avaje.ebeaninternal.api.TransactionEventTable.TableIUD; import com.avaje.ebeaninternal.server.cache.CachedBeanData; -import com.avaje.ebeaninternal.server.cache.CachedManyIds; import com.avaje.ebeaninternal.server.core.CacheOptions; import com.avaje.ebeaninternal.server.core.DefaultSqlUpdate; import com.avaje.ebeaninternal.server.core.InternString; @@ -33,13 +32,13 @@ import com.avaje.ebeaninternal.server.query.CQueryPlan; import com.avaje.ebeaninternal.server.query.CQueryPlanStats.Snapshot; import com.avaje.ebeaninternal.server.query.SplitName; import com.avaje.ebeaninternal.server.querydefn.OrmQueryDetail; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.TypeManager; import com.avaje.ebeaninternal.util.SortByClause; import com.avaje.ebeaninternal.util.SortByClause.Property; import com.avaje.ebeaninternal.util.SortByClauseParser; -import com.fasterxml.jackson.core.JsonParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1894,11 +1893,11 @@ public class BeanDescriptor implements MetaBeanInfo { jsonHelp.jsonWriteProperties(writeJson, bean); } - public T jsonRead(JsonParser parser, String path) throws IOException { - return jsonHelp.jsonRead(parser, path); + public T jsonRead(ReadJson jsonRead, String path) throws IOException { + return jsonHelp.jsonRead(jsonRead, path); } - protected T jsonReadObject(JsonParser parser, String path) throws IOException { - return jsonHelp.jsonReadObject(parser, path); + protected T jsonReadObject(ReadJson jsonRead, String path) throws IOException { + return jsonHelp.jsonReadObject(jsonRead, path); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java index 25e38b559..ce6ccac09 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanDescriptorJsonHelp.java @@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy; import com.avaje.ebean.bean.EntityBean; import com.avaje.ebean.text.json.EJson; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; import com.avaje.ebeaninternal.server.text.json.WriteJson.WriteBean; import com.fasterxml.jackson.core.JsonParseException; @@ -9,6 +10,8 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; public class BeanDescriptorJsonHelp { @@ -50,8 +53,9 @@ public class BeanDescriptorJsonHelp { @SuppressWarnings("unchecked") - public T jsonRead(JsonParser parser, String path) throws IOException { + public T jsonRead(ReadJson jsonRead, String path) throws IOException { + JsonParser parser = jsonRead.getParser(); if (parser.getCurrentToken() == JsonToken.START_OBJECT) { // start object token read by Jackson already } else { @@ -66,7 +70,7 @@ public class BeanDescriptorJsonHelp { } if (desc.inheritInfo == null) { - return jsonReadObject(parser, path); + return jsonReadObject(jsonRead, path); } // check for the discriminator value to determine the correct sub type @@ -83,8 +87,8 @@ public class BeanDescriptorJsonHelp { BeanProperty property = desc.getBeanProperty(propName); if (property != null) { EntityBean bean = desc.createEntityBean(); - property.jsonRead(parser, bean); - return jsonReadProperties(parser, bean); + property.jsonRead(jsonRead, bean); + return jsonReadProperties(jsonRead, bean, path); } String msg = "Error reading inheritance discriminator, expected property ["+discColumn+"] but got [" + propName + "] ?"; throw new JsonParseException(msg, parser.getCurrentLocation()); @@ -95,29 +99,39 @@ public class BeanDescriptorJsonHelp { // determine the sub type for this particular json object InheritInfo localInheritInfo = inheritInfo.readType(discValue); BeanDescriptor localDescriptor = localInheritInfo.getBeanDescriptor(); - return (T) localDescriptor.jsonReadObject(parser, path); + return (T) localDescriptor.jsonReadObject(jsonRead, path); } - protected T jsonReadObject(JsonParser parser, String path) throws IOException { + protected T jsonReadObject(ReadJson readJson, String path) throws IOException { EntityBean bean = desc.createEntityBean(); - return jsonReadProperties(parser, bean); + return jsonReadProperties(readJson, bean, path); } @SuppressWarnings("unchecked") - protected T jsonReadProperties(JsonParser parser, EntityBean bean) throws IOException { + protected T jsonReadProperties(ReadJson readJson, EntityBean bean, String path) throws IOException { + + if (path != null) { + readJson.pushPath(path); + } + + // unmapped properties, send to JsonReadBeanVisitor later + Map unmappedProperties = null; do { - + JsonParser parser = readJson.getParser(); JsonToken event = parser.nextToken(); if (JsonToken.FIELD_NAME == event) { String key = parser.getCurrentName(); BeanProperty p = desc.getBeanProperty(key); if (p != null) { - p.jsonRead(parser, bean); + p.jsonRead(readJson, bean); } else { - // unknown property ... read and ignore - EJson.parse(parser); + // read an unmapped property + if (unmappedProperties == null) { + unmappedProperties = new LinkedHashMap(); + } + unmappedProperties.put(key, EJson.parse(parser)); } } else if (JsonToken.END_OBJECT == event) { @@ -128,6 +142,13 @@ public class BeanDescriptorJsonHelp { } } while (true); + + // visit JsonReadBeanVisitor (if registered for this path) + readJson.beanVisitor(bean, unmappedProperties); + + if (path != null) { + readJson.popPath(); + } return (T)bean; } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java index 9ccaf53c4..03a1601d3 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java @@ -17,10 +17,10 @@ import com.avaje.ebeaninternal.server.properties.BeanPropertyGetter; import com.avaje.ebeaninternal.server.properties.BeanPropertySetter; import com.avaje.ebeaninternal.server.query.SqlBeanLoad; import com.avaje.ebeaninternal.server.query.SqlJoinType; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.ScalarType; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; import javax.persistence.PersistenceException; @@ -1076,7 +1076,7 @@ public class BeanProperty implements ElPropertyValue { } } - public void jsonRead(JsonParser ctx, EntityBean bean) throws IOException { + public void jsonRead(ReadJson ctx, EntityBean bean) throws IOException { if (!jsonDeserialize) { return; } @@ -1086,7 +1086,7 @@ public class BeanProperty implements ElPropertyValue { setValue(bean, null); } else { // expect to read non-null json value - Object objValue = scalarType.jsonRead(ctx, event); + Object objValue = scalarType.jsonRead(ctx.getParser(), event); setValue(bean, objValue); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java index 878050f53..be19b6cb5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocMany.java @@ -13,8 +13,8 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany; import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; -import com.fasterxml.jackson.core.JsonParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -878,7 +878,7 @@ public class BeanPropertyAssocMany extends BeanPropertyAssoc { } } - public void jsonRead(JsonParser parser, EntityBean parentBean) throws IOException { - jsonHelp.jsonRead(parser, parentBean); + public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException { + jsonHelp.jsonRead(readJson, parentBean); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java index f5f2e7ae5..873daa9b0 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocManyJsonHelp.java @@ -5,6 +5,7 @@ import java.io.IOException; import com.avaje.ebean.bean.BeanCollection; import com.avaje.ebean.bean.BeanCollectionAdd; import com.avaje.ebean.bean.EntityBean; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -17,12 +18,13 @@ public class BeanPropertyAssocManyJsonHelp { this.many = many; } - public void jsonRead(JsonParser parser, EntityBean parentBean) throws IOException { + public void jsonRead(ReadJson readJson, EntityBean parentBean) throws IOException { if (!this.many.jsonDeserialize) { return; } + JsonParser parser = readJson.getParser(); JsonToken event = parser.nextToken(); if (JsonToken.VALUE_NULL == event) { return; @@ -34,7 +36,7 @@ public class BeanPropertyAssocManyJsonHelp { BeanCollection collection = many.createEmpty(parentBean); BeanCollectionAdd add = many.getBeanCollectionAdd(collection, null); do { - EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(parser, many.name); + EntityBean detailBean = (EntityBean) many.targetDescriptor.jsonRead(readJson, many.name); if (detailBean == null) { // read the entire array break; diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java index 452e1a55d..9adbeecb8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyAssocOne.java @@ -25,8 +25,8 @@ import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SplitName; import com.avaje.ebeaninternal.server.query.SqlBeanLoad; import com.avaje.ebeaninternal.server.query.SqlJoinType; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; -import com.fasterxml.jackson.core.JsonParser; /** * Property mapped to a joined bean. @@ -843,9 +843,9 @@ public class BeanPropertyAssocOne extends BeanPropertyAssoc { } @Override - public void jsonRead(JsonParser parser, EntityBean bean) throws IOException { + public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException { if (jsonDeserialize && targetDescriptor != null) { - T assocBean = targetDescriptor.jsonRead(parser, name); + T assocBean = targetDescriptor.jsonRead(readJson, name); setValue(bean, assocBean); } } diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java index a4a043e42..ac241d8d6 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java +++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanPropertyCompound.java @@ -13,11 +13,11 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound; import com.avaje.ebeaninternal.server.el.ElPropertyChainBuilder; import com.avaje.ebeaninternal.server.el.ElPropertyValue; import com.avaje.ebeaninternal.server.query.SqlBeanLoad; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import com.avaje.ebeaninternal.server.text.json.WriteJson; import com.avaje.ebeaninternal.server.type.CtCompoundProperty; import com.avaje.ebeaninternal.server.type.CtCompoundPropertyElAdapter; import com.avaje.ebeaninternal.server.type.CtCompoundType; -import com.fasterxml.jackson.core.JsonParser; /** * Property mapped to an Immutable Compound Value Object. @@ -177,18 +177,17 @@ public class BeanPropertyCompound extends BeanProperty { } } - public void jsonRead(JsonParser ctx, EntityBean bean) throws IOException { + @Override + public void jsonRead(ReadJson readJson, EntityBean bean) throws IOException { if (!jsonDeserialize) { return; } - Object value = EJson.parse(ctx); - if (value == null) { + Map map = EJson.parseObject(readJson.getParser()); + if (map == null) { setValue(bean, null); } else { - @SuppressWarnings("unchecked") - Map map = (Map) value; Object objValue = compoundType.jsonConvert(map); setValue(bean, objValue); } diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java index a3f0ed7a0..bd00dbfde 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/DJsonContext.java @@ -1,11 +1,8 @@ package com.avaje.ebeaninternal.server.text.json; import com.avaje.ebean.bean.EntityBean; -import com.avaje.ebean.text.json.EJson; +import com.avaje.ebean.text.json.*; import com.avaje.ebean.text.PathProperties; -import com.avaje.ebean.text.json.JsonContext; -import com.avaje.ebean.text.json.JsonIOException; -import com.avaje.ebean.text.json.JsonWriteOptions; import com.avaje.ebeaninternal.api.SpiEbeanServer; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.util.ParamTypeHelper; @@ -56,15 +53,29 @@ public class DJsonContext implements JsonContext { return toBean(cls, new StringReader(json)); } + @Override + public T toBean(Class cls, String json, JsonReadOptions options) throws JsonIOException { + return toBean(cls, new StringReader(json), options); + } + public T toBean(Class cls, Reader jsonReader) throws JsonIOException { return toBean(cls, createParser(jsonReader)); } - public T toBean(Class cls, JsonParser parser) throws JsonIOException { + public T toBean(Class cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException { + return toBean(cls, createParser(jsonReader), options); + } + public T toBean(Class cls, JsonParser parser) throws JsonIOException { + return toBean(cls, parser, null); + } + + public T toBean(Class cls, JsonParser parser, JsonReadOptions options) throws JsonIOException { + + ReadJson readJson = new ReadJson(parser, options); try { BeanDescriptor d = getDescriptor(cls); - return d.jsonRead(parser, null); + return d.jsonRead(readJson, null); } catch (IOException e) { throw new JsonIOException(e); } @@ -74,13 +85,26 @@ public class DJsonContext implements JsonContext { return toList(cls, new StringReader(json)); } + @Override + public List toList(Class cls, String json, JsonReadOptions options) throws JsonIOException { + return toList(cls, new StringReader(json), options); + } public List toList(Class cls, Reader jsonReader) throws JsonIOException { return toList(cls, createParser(jsonReader)); } - public List toList(Class cls, JsonParser src) throws JsonIOException { + public List toList(Class cls, Reader jsonReader, JsonReadOptions options) throws JsonIOException { + return toList(cls, createParser(jsonReader), options); + } + public List toList(Class cls, JsonParser src) throws JsonIOException { + return toList(cls, src, null); + } + + public List toList(Class cls, JsonParser src, JsonReadOptions options) throws JsonIOException { + + ReadJson readJson = new ReadJson(src, options); try { BeanDescriptor d = getDescriptor(cls); @@ -92,7 +116,7 @@ public class DJsonContext implements JsonContext { } do { - T bean = d.jsonRead(src, null); + T bean = d.jsonRead(readJson, null); if (bean == null) { break; } else { diff --git a/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java new file mode 100644 index 000000000..f21e4701b --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/text/json/ReadJson.java @@ -0,0 +1,87 @@ +package com.avaje.ebeaninternal.server.text.json; + +import com.avaje.ebean.text.json.JsonReadBeanVisitor; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; + +import java.io.IOException; +import java.util.Map; + +/** + * Context for JSON read processing. + */ +public class ReadJson { + + /** + * Jackson parser. + */ + final JsonParser parser; + + /** + * Stack of the path - used to find the appropriate JsonReadBeanVisitor. + */ + final PathStack pathStack; + + /** + * Map of the JsonReadBeanVisitor keyed by path. + */ + final Map> visitorMap; + + /** + * Construct with parser and readOptions. + */ + public ReadJson(JsonParser parser, JsonReadOptions readOptions) { + this.parser = parser; + + // only create visitorMap, pathStack if needed ... + this.visitorMap = (readOptions == null) ? null : readOptions.getVisitorMap(); + this.pathStack = (visitorMap == null) ? null : new PathStack(); + } + + /** + * Return the JsonParser. + */ + public JsonParser getParser() { + return parser; + } + + /** + * Return the next JsonToken from the underlying parser. + */ + public JsonToken nextToken() throws IOException { + return parser.nextToken(); + } + + /** + * Push the path onto the stack (traversing a 1-M or M-1 etc) + */ + public void pushPath(String path) { + if (pathStack != null) { + pathStack.pushPathKey(path); + } + } + + /** + * Pop the path stack. + */ + public void popPath() { + if (pathStack != null) { + pathStack.pop(); + } + } + + /** + * If there is a JsonReadBeanVisitor registered to the current path then + * call it's visit method with the bean and unmappedProperties. + */ + @SuppressWarnings(value = "unchecked") + public void beanVisitor(Object bean, Map unmappedProperties) { + if (visitorMap != null) { + JsonReadBeanVisitor visitor = visitorMap.get(pathStack.peekWithNull()); + if (visitor != null) { + visitor.visit(bean, unmappedProperties); + } + } + } +} diff --git a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java index 5ea2301df..942f8e604 100644 --- a/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java +++ b/src/test/java/com/avaje/ebean/text/json/JsonContextTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import java.io.StringReader; import java.io.StringWriter; +import java.util.Map; import static org.junit.Assert.*; @@ -60,6 +61,57 @@ public class JsonContextTest { assertEquals("rob", customer.getName()); } + class CustReadVisitor implements JsonReadBeanVisitor { + + Customer bean; + Map unmapped; + + @Override + public void visit(Customer bean, Map unmapped) { + this.bean = bean; + this.unmapped = unmapped; + } + } + + @Test + public void test_unknownProperty_withVisitor() { + + String jsonWithUnknown = "{\"id\":42,\"unknownProp\":\"foo\",\"name\":\"rob\",\"version\":1,\"extraProp\":{\"name\":\"foobie\",\"sim\":\"bo\"}}"; + + CustReadVisitor custReadVisitor = new CustReadVisitor(); + JsonReadOptions options = new JsonReadOptions(); + options.addRootVisitor(custReadVisitor); + + + Customer customer = Ebean.json().toBean(Customer.class, jsonWithUnknown, options); + assertEquals(Integer.valueOf(42), customer.getId()); + assertEquals("rob", customer.getName()); + + assertSame(customer, custReadVisitor.bean); + assertEquals("foo", custReadVisitor.unmapped.get("unknownProp")); + assertEquals(2, custReadVisitor.unmapped.size()); + assertEquals("foobie", ((Map)custReadVisitor.unmapped.get("extraProp")).get("name")); + assertEquals("bo", ((Map) custReadVisitor.unmapped.get("extraProp")).get("sim")); + + } + + @Test + public void test_withVisitor_noUnmapped() { + + String someJsonAllKnown = "{\"id\":42,\"name\":\"rob\",\"version\":1}"; + + CustReadVisitor custReadVisitor = new CustReadVisitor(); + JsonReadOptions options = new JsonReadOptions(); + options.addRootVisitor(custReadVisitor); + + Customer customer = Ebean.json().toBean(Customer.class, someJsonAllKnown, options); + assertEquals(Integer.valueOf(42), customer.getId()); + assertEquals("rob", customer.getName()); + + assertSame(customer, custReadVisitor.bean); + assertNull(custReadVisitor.unmapped); + } + @Test public void testCreateGenerator() throws Exception { diff --git a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java index 9eb633cbc..252544727 100644 --- a/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java +++ b/src/test/java/com/avaje/tests/text/json/TestJsonBeanDescriptorParse.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.StringReader; import java.util.Set; +import com.avaje.ebeaninternal.server.text.json.ReadJson; import org.junit.Assert; import org.junit.Test; @@ -27,8 +28,10 @@ public class TestJsonBeanDescriptorParse extends BaseTestCase { StringReader reader = new StringReader("{\"id\":123,\"name\":\"Hello rob\"}"); JsonParser parser = server.json().createParser(reader); + + ReadJson readJson = new ReadJson(parser, null); - Customer customer = (Customer)descriptor.jsonRead(parser, null); + Customer customer = descriptor.jsonRead(readJson, null); Assert.assertEquals(Integer.valueOf(123), customer.getId()); Assert.assertEquals("Hello rob", customer.getName()); diff --git a/src/test/java/com/avaje/tests/text/json/TestTextJsonSimple.java b/src/test/java/com/avaje/tests/text/json/TestTextJsonSimple.java index 82c18b3bb..30a039d75 100644 --- a/src/test/java/com/avaje/tests/text/json/TestTextJsonSimple.java +++ b/src/test/java/com/avaje/tests/text/json/TestTextJsonSimple.java @@ -5,6 +5,10 @@ import com.avaje.ebean.Ebean; import com.avaje.ebean.EbeanServer; import com.avaje.ebean.text.PathProperties; import com.avaje.ebean.text.json.JsonContext; +import com.avaje.ebean.text.json.JsonReadBeanVisitor; +import com.avaje.ebean.text.json.JsonReadOptions; +import com.avaje.tests.model.basic.Address; +import com.avaje.tests.model.basic.Contact; import com.avaje.tests.model.basic.Customer; import com.avaje.tests.model.basic.ResetBasicData; import org.junit.Assert; @@ -12,9 +16,45 @@ import org.junit.Test; import java.io.IOException; import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; public class TestTextJsonSimple extends BaseTestCase { + class CustJsonRead implements JsonReadBeanVisitor { + + Customer bean; Map unmapped; + + @Override + public void visit(Customer bean, Map unmapped) { + this.bean = bean; + this.unmapped = unmapped; + } + } + + class ContactJsonRead implements JsonReadBeanVisitor { + + Contact bean; Map unmapped; + + @Override + public void visit(Contact bean, Map unmapped) { + this.bean = bean; + this.unmapped = unmapped; + } + } + + class AddressJsonRead implements JsonReadBeanVisitor
{ + + Address bean; Map unmapped; + + @Override + public void visit(Address bean, Map unmapped) { + this.bean = bean; + this.unmapped = unmapped; + } + } + @Test public void test() throws IOException { @@ -37,7 +77,21 @@ public class TestTextJsonSimple extends BaseTestCase { Assert.assertTrue(jsonOutput.contains("\"selected\":")); List mList = json.toList(Customer.class, jsonOutput); - Assert.assertEquals(list.size(), mList.size()); + assertEquals(list.size(), mList.size()); + + CustJsonRead custJsonRead = new CustJsonRead(); + ContactJsonRead contactJsonRead = new ContactJsonRead(); + AddressJsonRead addressJsonRead = new AddressJsonRead(); + + JsonReadOptions options = new JsonReadOptions(); + options.addRootVisitor(custJsonRead); + options.addVisitor("contacts", contactJsonRead); + options.addVisitor("billingAddress", addressJsonRead); + + List customers = json.toList(Customer.class, jsonOutput, options); + + assertEquals(list.size(), customers.size()); + } @Test @@ -61,7 +115,7 @@ public class TestTextJsonSimple extends BaseTestCase { Assert.assertTrue(jsonOutput.contains("\"selected\":")); List mList = json.toList(Customer.class, jsonOutput); - Assert.assertEquals(list.size(), mList.size()); + assertEquals(list.size(), mList.size()); } @Test