Skip to content

Commit

Permalink
Adding support for field transformation (#52)
Browse files Browse the repository at this point in the history
  • Loading branch information
monitorjbl authored and Taylor Jones committed Sep 27, 2017
1 parent eb46730 commit d448f3c
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 37 deletions.
84 changes: 58 additions & 26 deletions json-view/src/main/java/com/monitorjbl/json/JsonViewSerializer.java
Expand Up @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -620,4 +642,14 @@ private <T, V> Map<T, V> fitToMaxSize(Map<T, V> 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;
}
}
}
45 changes: 39 additions & 6 deletions 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<String> includes = new HashSet<>();
private final Set<String> excludes = new HashSet<>();
private final Map<String, BiFunction<Object, Object, Object>> 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 <X, Y, Z> Match transform(String field, BiFunction<X, Y, Z> transformer) {
transforms.put(field, (BiFunction<Object, Object, Object>) transformer);
return this;
}

Set<String> getIncludes() {
return includes;
}
Expand All @@ -36,6 +63,10 @@ Set<String> getExcludes() {
return excludes;
}

Map<String, BiFunction<Object, Object, Object>> getTransforms() {
return transforms;
}

public static Match match() {
return new Match();
}
Expand All @@ -45,6 +76,7 @@ public String toString() {
return "Match{" +
"includes=" + includes +
", excludes=" + excludes +
", transforms=" + transforms +
'}';
}

Expand All @@ -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;
}
}
Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -872,7 +872,7 @@ public void testPathFirstMatch_defaultBehavior() throws Exception {
ref.setStr2("erer");
ref.setRecursion(ref);

Consumer<Map<String, Object>> doTest = obj->{
Consumer<Map<String, Object>> doTest = obj -> {
assertEquals(ref.getInt1(), obj.get("int1"));
assertEquals(ref.getStr1(), obj.get("str1"));
assertEquals(ref.getStr2(), obj.get("str2"));
Expand Down Expand Up @@ -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<String, Object> 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"));
}
}

0 comments on commit d448f3c

Please sign in to comment.