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 9b4aeb0..dd5139d 100644 --- a/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java +++ b/json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java @@ -312,10 +312,14 @@ void writeObject(Object obj) throws IOException { for(Field field : fields) { try { field.setAccessible(true); - Object val = field.get(obj); //if the field has a serializer annotation on it, serialize with it - if(valueAllowed(val, obj.getClass()) && fieldAllowed(field, obj.getClass())) { + if(fieldAllowed(field, obj.getClass())) { + Object val = readField(obj, field); + if(!valueAllowed(val, obj.getClass())) { + continue; + } + String name = getFieldName(field); jgen.writeFieldName(name); @@ -376,34 +380,13 @@ private Match classMatchSearch(Class declaringClass) { @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 = ""; - } - } + MatchPrefixTuple tuple = getMatchPrefix(declaringClass); + String prefix = tuple.prefix; + Match match = tuple.match; //if there is a match, respect it if(match != null) { @@ -437,6 +420,45 @@ boolean fieldAllowed(Field field, Class declaringClass) { } } + MatchPrefixTuple getMatchPrefix(Class declaringClass) { + String prefix = currentPath.length() > 0 ? currentPath + "." : ""; + + // 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 = ""; + } + } + + return new MatchPrefixTuple(match, prefix); + } + + Object readField(Object obj, Field field) throws IllegalAccessException { + MatchPrefixTuple tuple = getMatchPrefix(obj.getClass()); + if(tuple.match != null && tuple.match.getTransforms().containsKey(tuple.prefix + field.getName())) { + return tuple.match.getTransforms().get(tuple.prefix + field.getName()).apply(obj, field.get(obj)); + } else { + return field.get(obj); + } + } + void write(String fieldName, Object value) throws IOException { if(fieldName != null) { path.push(fieldName); @@ -620,4 +642,14 @@ private Map fitToMaxSize(Map map) { return map; } } + + private static class MatchPrefixTuple { + private final Match match; + private final String prefix; + + public MatchPrefixTuple(Match match, String prefix) { + this.match = match; + this.prefix = prefix; + } + } } diff --git a/json-view/src/main/java/com/monitorjbl/json/Match.java b/json-view/src/main/java/com/monitorjbl/json/Match.java index 0c1b41f..cb9bccd 100644 --- a/json-view/src/main/java/com/monitorjbl/json/Match.java +++ b/json-view/src/main/java/com/monitorjbl/json/Match.java @@ -1,33 +1,60 @@ package com.monitorjbl.json; -import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; -import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; public class Match { private final Set includes = new HashSet<>(); private final Set excludes = new HashSet<>(); + private final Map> transforms = new HashMap<>(); Match() { } + /** + * Mark fields for inclusion during serialization. + * + * @param fields The fields to include + * @return Match + */ public Match include(String... fields) { - if (fields != null) { + if(fields != null) { includes.addAll(Arrays.asList(fields)); } return this; } + /** + * Mark fields for exclusion during serialization. + * + * @param fields The fields to exclude + * @return Match + */ public Match exclude(String... fields) { - if (fields != null) { + if(fields != null) { excludes.addAll(Arrays.asList(fields)); } return this; } + /** + * Mark a field for transformation during serialization. + * + * @param field The fields to include + * @param transformer The function to transform the field. Will be provided with the whole object and the field. + * @return Match + */ + @SuppressWarnings("unchecked") + public Match transform(String field, BiFunction transformer) { + transforms.put(field, (BiFunction) transformer); + return this; + } + Set getIncludes() { return includes; } @@ -36,6 +63,10 @@ Set getExcludes() { return excludes; } + Map> getTransforms() { + return transforms; + } + public static Match match() { return new Match(); } @@ -45,6 +76,7 @@ public String toString() { return "Match{" + "includes=" + includes + ", excludes=" + excludes + + ", transforms=" + transforms + '}'; } @@ -56,14 +88,15 @@ public boolean equals(Object o) { Match match = (Match) o; if(includes != null ? !includes.equals(match.includes) : match.includes != null) return false; - return excludes != null ? excludes.equals(match.excludes) : match.excludes == null; - + if(excludes != null ? !excludes.equals(match.excludes) : match.excludes != null) return false; + return transforms != null ? transforms.equals(match.transforms) : match.transforms == null; } @Override public int hashCode() { int result = includes != null ? includes.hashCode() : 0; result = 31 * result + (excludes != null ? excludes.hashCode() : 0); + result = 31 * result + (transforms != null ? transforms.hashCode() : 0); return result; } } 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 9806a40..33a261b 100644 --- a/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java +++ b/json-view/src/test/java/com/monitorjbl/json/JsonViewSerializerTest.java @@ -298,7 +298,7 @@ public void testMapOfSubobjects() throws IOException { ref.setMapOfObjects(ImmutableMap.of( "key1", new TestSubobject("test1"), "key2", new TestSubobject("test2", new TestSubobject("test3")) - )); + )); String serialized = sut.writeValueAsString( JsonView.with(ref) .onClass(TestObject.class, match() @@ -323,7 +323,7 @@ public void testMapWithIntKeys() throws IOException { ref.setMapWithIntKeys(ImmutableMap.of( 1, "red", 2, "green" - )); + )); String serialized = sut.writeValueAsString( JsonView.with(ref) .onClass(TestObject.class, match() @@ -409,7 +409,7 @@ public void testBlanketExclude() throws Exception { ref.setMapOfObjects(ImmutableMap.of( "key1", new TestSubobject("test1"), "key2", new TestSubobject("test2", new TestSubobject("test3")) - )); + )); String serialized = sut.writeValueAsString( JsonView.with(ref) @@ -434,7 +434,7 @@ public void testBlanketInclude() throws Exception { ref.setMapOfObjects(ImmutableMap.of( "key1", new TestSubobject("test1"), "key2", new TestSubobject("test2", new TestSubobject("test3")) - )); + )); String serialized = sut.writeValueAsString( JsonView.with(ref) @@ -872,7 +872,7 @@ public void testPathFirstMatch_defaultBehavior() throws Exception { ref.setStr2("erer"); ref.setRecursion(ref); - Consumer> doTest = obj->{ + Consumer> doTest = obj -> { assertEquals(ref.getInt1(), obj.get("int1")); assertEquals(ref.getStr1(), obj.get("str1")); assertEquals(ref.getStr2(), obj.get("str2")); @@ -911,4 +911,27 @@ public void testPathFirstMatch_defaultBehavior() throws Exception { .exclude("*"))); doTest.accept(sut.readValue(serialized, HashMap.class)); } + + @Test + public void testFieldTransform() throws Exception { + TestObject ref = new TestObject(); + ref.setInt1(1); + ref.setStr1("sdfdsf"); + + sut = new ObjectMapper().registerModule(new JsonViewModule(serializer)); + + String serialized = sut.writeValueAsString(JsonView.with(ref) + .onClass(TestObject.class, match() + .exclude("*") + .include("str1", "str2") + .transform("str1", (TestObject t, String f) -> f.toUpperCase()) + .transform("str2", (TestObject t, String f) -> t.getStr1()))); + Map obj = sut.readValue(serialized, HashMap.class); + + assertNull(obj.get("int1")); + assertNotNull(obj.get("str1")); + assertEquals(ref.getStr1().toUpperCase(), obj.get("str1")); + assertNotNull(obj.get("str2")); + assertEquals(ref.getStr1(), obj.get("str2")); + } }