Skip to content

Instantly share code, notes, and snippets.

@UnquietCode
Last active April 8, 2019 19:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save UnquietCode/7954032 to your computer and use it in GitHub Desktop.
Save UnquietCode/7954032 to your computer and use it in GitHub Desktop.
Jackson module which can instantiate interface types using a proxy.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.Deserializers;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.IOException;
import java.util.Iterator;
/**
* @author Ben Fagin
* @version 2013-12-13
*/
public class MapperModule extends Module {
@Override
public String getModuleName() {
return "ModelMapper";
}
@Override
public Version version() {
return new Version(2, 0, 0, null, null, null);
}
@Override
public void setupModule(SetupContext context) {
context.addDeserializers(new Deserializers.Base() {
public @Override JsonDeserializer<?> findBeanDeserializer(final JavaType type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {
if (Model.class.isAssignableFrom(type.getRawClass())) {
return new JsonDeserializer<Object>() {
public @Override Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
Iterator<ObjectNode> obj = jp.readValuesAs(ObjectNode.class);
return ModelMapper.map(type.getRawClass(), obj.next());
}
};
}
return super.findBeanDeserializer(type, config, beanDesc);
}
});
}
}
/**
* Marker interface for all model objects.
*
* @author Ben Fagin
* @version 2013-12-13
*/
interface Model {
// nothing here, as this is just a marker
}
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.POJONode;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Ben Fagin
* @version 2013-12-13
*/
public class ModelMapper {
@SuppressWarnings("unchecked")
public static <T> T map(Class<T> klaus, final ObjectNode data) {
checkNotNull(data);
checkArgument(klaus != null && klaus.isInterface(), "interfaces only please");
return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{klaus}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String property = method.getName().substring(3);
property = new String(new char[]{property.charAt(0)}).toLowerCase() + property.substring(1);
// getter
if (method.getName().startsWith("get") && method.getParameterTypes().length == 0) {
return getProperty(data, property, method);
}
// setter
if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
data.putPOJO(property, args[0]);
return null;
}
throw new RuntimeException("Not a property: "+method.getName());
}
});
}
private static Object getProperty(ObjectNode data, String property, Method method) {
Class<?> returnType = method.getReturnType();
JsonNode value = data.get(property);
if (Integer.class.equals(returnType) || int.class.equals(returnType)) {
return value.asInt();
}
else if (Double.class.equals(returnType) || double.class.equals(returnType)) {
return value.asDouble();
}
else if (Long.class.equals(returnType) || long.class.equals(returnType)) {
return value.asLong();
}
else if (Boolean.class.equals(returnType) || boolean.class.equals(returnType)) {
return value.asBoolean();
}
else if (String.class.equals(returnType)) {
return value.asText();
}
else if (returnType.isInterface() && Model.class.isAssignableFrom(returnType)) {
if (value.isObject()) {
return map(returnType, (ObjectNode) value);
} else if (value.isPojo()) {
return ((POJONode) value).getPojo();
}
}
throw new RuntimeException("Unknown return type: "+returnType);
}
}
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.io.ByteArrayOutputStream;
/**
* @author Ben Fagin
* @version 2013-12-13
*/
public class Test {
interface Point extends Model {
int getX();
void setX(int x);
int getY();
void setY(int y);
Nub getNub();
void setNub(Nub nub);
}
interface Nub extends Model {
void setThing(int thing);
int getThing();
}
public static void main(String[] args) throws Exception {
final ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.registerModule(new MapperModule());
ObjectNode data = jsonMapper.createObjectNode();
data.put("x", 100);
data.put("y", 200);
data.putObject("nub").put("thing", 300);
Point point = jsonMapper.reader(Point.class).readValue(data);
System.out.println(point.getX());
System.out.println(point.getY());
System.out.println(point.getNub().getThing());
point.setX(111);
point.setY(222);
point.setNub(new Nub() {
int val = 333;
public void setThing(int thing) {
val = thing;
}
public int getThing() {
return val;
}
});
ByteArrayOutputStream os = new ByteArrayOutputStream();
System.out.println(point.getX());
System.out.println(point.getY());
System.out.println(point.getNub().getThing());
jsonMapper.writerWithType(Point.class).writeValue(os, point);
System.out.println(os.toString());
os.reset();
point.getNub().setThing(400);
System.out.println(point.getNub().getThing());
jsonMapper.writerWithType(Point.class).writeValue(os, point);
System.out.println(os.toString());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment