diff --git a/json-view/src/main/java/com/monitorjbl/json/JsonView.java b/json-view/src/main/java/com/monitorjbl/json/JsonView.java index e788241..6d8baa9 100644 --- a/json-view/src/main/java/com/monitorjbl/json/JsonView.java +++ b/json-view/src/main/java/com/monitorjbl/json/JsonView.java @@ -9,6 +9,7 @@ public class JsonView { protected final T value; protected final Map, Match> matches = new HashMap<>(); + protected MatcherBehavior matcherBehavior; protected JsonView(T value) { this.value = value; @@ -27,6 +28,11 @@ public JsonView onClass(Class cls, Match match) { return this; } + public JsonView withMatcherBehavior(MatcherBehavior matcherBehavior) { + this.matcherBehavior = matcherBehavior; + return this; + } + public static JsonView with(E value) { return new JsonView<>(value); } diff --git a/json-view/src/main/java/com/monitorjbl/json/JsonViewModule.java b/json-view/src/main/java/com/monitorjbl/json/JsonViewModule.java index d72f274..82b7fd0 100644 --- a/json-view/src/main/java/com/monitorjbl/json/JsonViewModule.java +++ b/json-view/src/main/java/com/monitorjbl/json/JsonViewModule.java @@ -18,8 +18,14 @@ public JsonViewModule(JsonViewSerializer jsonView) { this.jsonView = jsonView; } + public JsonViewModule withDefaultMatcherBehavior(MatcherBehavior matcherBehavior){ + this.jsonView.setDefaultMatcherBehavior(matcherBehavior); + return this; + } + public JsonViewModule registerSerializer(Class cls, JsonSerializer serializer) { jsonView.registerCustomSerializer(cls, serializer); return this; } + } diff --git a/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java b/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java index 4396d53..9b4aeb0 100644 --- a/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java +++ b/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java @@ -33,6 +33,9 @@ import java.util.UUID; import java.util.regex.Pattern; +import static com.monitorjbl.json.MatcherBehavior.CLASS_FIRST; +import static com.monitorjbl.json.MatcherBehavior.PATH_FIRST; + public class JsonViewSerializer extends JsonSerializer { /** @@ -45,6 +48,8 @@ public class JsonViewSerializer extends JsonSerializer { */ private Map, JsonSerializer> customSerializersMap = null; + private MatcherBehavior defaultMatcherBehavior = CLASS_FIRST; + public JsonViewSerializer() { this(1024); } @@ -95,6 +100,15 @@ public void unregisterCustomSerializer(Class cls) { } } + /** + * Set the default matcher behavior to be used if the {@link JsonView} object to + * be serialized does not specify one. + * + * @param defaultMatcherBehavior The default behavior to use + */ + public void setDefaultMatcherBehavior(MatcherBehavior defaultMatcherBehavior) { + this.defaultMatcherBehavior = defaultMatcherBehavior; + } @Override public void serialize(JsonView result, JsonGenerator jgen, SerializerProvider serializers) throws IOException { @@ -339,14 +353,7 @@ && getAnnotation(cls, JsonSerialize.class) == null) } @SuppressWarnings("unchecked") - boolean fieldAllowed(Field field, Class declaringClass) { - String name = field.getName(); - String prefix = currentPath.length() > 0 ? currentPath + "." : ""; - if(Modifier.isStatic(field.getModifiers())) { - return false; - } - - //search for matching class + private Match classMatchSearch(Class declaringClass) { Match match = null; Class cls = declaringClass; while(!cls.equals(Object.class) && match == null) { @@ -363,10 +370,39 @@ boolean fieldAllowed(Field field, Class declaringClass) { } cls = cls.getSuperclass(); } - if(match == null) { - match = currentMatch; - } else { - prefix = ""; + return match; + } + + @SuppressWarnings("unchecked") + boolean fieldAllowed(Field field, Class declaringClass) { + String name = field.getName(); + String prefix = currentPath.length() > 0 ? currentPath + "." : ""; + if(Modifier.isStatic(field.getModifiers())) { + return false; + } + + // Determine matcher behavior + MatcherBehavior currentBehavior = result.matcherBehavior; + if(currentBehavior == null) { + currentBehavior = JsonViewSerializer.this.defaultMatcherBehavior; + } + + //search for matching class + Match match = null; + if(currentBehavior == CLASS_FIRST) { + match = classMatchSearch(declaringClass); + if(match == null) { + match = currentMatch; + } else { + prefix = ""; + } + } else if(currentBehavior == PATH_FIRST) { + if(currentMatch != null) { + match = currentMatch; + } else { + match = classMatchSearch(declaringClass); + prefix = ""; + } } //if there is a match, respect it diff --git a/json-view/src/main/java/com/monitorjbl/json/MatcherBehavior.java b/json-view/src/main/java/com/monitorjbl/json/MatcherBehavior.java new file mode 100644 index 0000000..6d90f19 --- /dev/null +++ b/json-view/src/main/java/com/monitorjbl/json/MatcherBehavior.java @@ -0,0 +1,18 @@ +package com.monitorjbl.json; + +/** + * Dictates the order in which to search for matches when serializing objects. + */ +public enum MatcherBehavior { + /** + * Check for matches on an class first before using the + * current path matcher + */ + CLASS_FIRST, + + /** + * Check for matches against the current path first before + * looking for a matcher on the class + */ + PATH_FIRST +} diff --git a/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java b/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java index f284430..9806a40 100644 --- a/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java +++ b/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.function.Consumer; import static com.monitorjbl.json.Match.match; import static java.util.Arrays.asList; @@ -862,4 +863,52 @@ public void testJsonSerializeAnnotation() throws Exception { assertTrue(obj.get("customFieldSerializer") instanceof String); assertEquals(obj.get("customFieldSerializer"), "5[hello]"); } + + @Test + public void testPathFirstMatch_defaultBehavior() throws Exception { + TestObject ref = new TestObject(); + ref.setInt1(1); + ref.setStr1("sdfdsf"); + ref.setStr2("erer"); + ref.setRecursion(ref); + + Consumer> doTest = obj->{ + assertEquals(ref.getInt1(), obj.get("int1")); + assertEquals(ref.getStr1(), obj.get("str1")); + assertEquals(ref.getStr2(), obj.get("str2")); + assertNotNull(obj.get("recursion")); + + Map rec1 = (Map) obj.get("recursion"); + assertNotNull(rec1.get("recursion")); + assertEquals(ref.getStr1(), rec1.get("str1")); + assertNull(rec1.get("str2")); + assertNull(rec1.get("int1")); + + Map rec2 = (Map) rec1.get("recursion"); + assertEquals(ref.getStr2(), rec2.get("str2")); + assertNull(rec2.get("str1")); + assertNull(rec2.get("int1")); + + assertNull(rec2.get("recursion")); + }; + + + // Perform test with behavior set at default level + sut = new ObjectMapper().registerModule(new JsonViewModule(serializer) + .withDefaultMatcherBehavior(MatcherBehavior.PATH_FIRST)); + String serialized = sut.writeValueAsString(JsonView.with(ref) + .onClass(TestObject.class, match() + .include("recursion", "recursion.str1", "recursion.recursion.str2", "str1", "str2", "int1") + .exclude("*"))); + doTest.accept(sut.readValue(serialized, HashMap.class)); + + // Perform test with behavior set at the JsonView level + sut = new ObjectMapper().registerModule(new JsonViewModule(serializer)); + serialized = sut.writeValueAsString(JsonView.with(ref) + .withMatcherBehavior(MatcherBehavior.PATH_FIRST) + .onClass(TestObject.class, match() + .include("recursion", "recursion.str1", "recursion.recursion.str2", "str1", "str2", "int1") + .exclude("*"))); + doTest.accept(sut.readValue(serialized, HashMap.class)); + } } diff --git a/json-view/src/test/java/com/monitorjbl/json/model/TestObject.java b/json-view/src/test/java/com/monitorjbl/json/model/TestObject.java index c250167..b15e1a8 100644 --- a/json-view/src/test/java/com/monitorjbl/json/model/TestObject.java +++ b/json-view/src/test/java/com/monitorjbl/json/model/TestObject.java @@ -57,6 +57,7 @@ public enum TestEnum {VALUE_A, VALUE_B} @JsonProperty private String jsonPropNoValue; private UUID uuid; + private TestObject recursion; public String getStr1() { return str1; @@ -289,4 +290,12 @@ public UUID getUuid() { public void setUuid(UUID uuid) { this.uuid = uuid; } + + public TestObject getRecursion() { + return recursion; + } + + public void setRecursion(TestObject recursion) { + this.recursion = recursion; + } }