To make it generic we need to be able to specify name which we would like to set in object for JSON primitive
. Some flexibility gives annotation approach. Let's define simple annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface JsonPrimitiveName {
String value();
}
Name means: in case primitive will appear in JSON
use value()
to get property name for given primitive. It binds JSON primitive
with POJO
field. Simple deserialiser which handles JSON object
and JSON primitive
:
class PrimitiveOrPojoJsonDeserializer extends JsonDeserializer implements ContextualDeserializer {
private String primitiveName;
private JavaType type;
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonDeserializer<Object> deserializer = ctxt.findRootValueDeserializer(type);
if (p.currentToken() == JsonToken.START_OBJECT) {
return deserializer.deserialize(p, ctxt);
} else if (p.currentToken() == JsonToken.VALUE_STRING) {
BeanDeserializer beanDeserializer = (BeanDeserializer) deserializer;
try {
Object instance = beanDeserializer.getValueInstantiator().getDefaultCreator().call();
SettableBeanProperty property = beanDeserializer.findProperty(primitiveName);
property.deserializeAndSet(p, ctxt, instance);
return instance;
} catch (Exception e) {
throw JsonMappingException.from(p, e.getMessage());
}
}
return null;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
JsonPrimitiveName annotation = property.getAnnotation(JsonPrimitiveName.class);
PrimitiveOrPojoJsonDeserializer deserializer = new PrimitiveOrPojoJsonDeserializer();
deserializer.primitiveName = annotation.value();
deserializer.type = property.getType();
return deserializer;
}
}
Now we need to annotate POJO
fields as below:
class Root {
@JsonPrimitiveName("value")
@JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Foo foo;
@JsonPrimitiveName("thing")
@JsonDeserialize(using = PrimitiveOrPojoJsonDeserializer.class)
private Fuzz fuzz;
// getters, setters
}
I assume that all classes are POJO
-s and follow all rules - have getters
, setters
and default constructor. In case constructor does not exist you need to change this beanDeserializer.getValueInstantiator().getDefaultCreator().call()
line somehow which fits your requirements.
Example app:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(jsonFile, Root.class));
}
}
Prints for shortened JSON
:
Root{foo=Foo{value='bar', baz='null'}, fuzz=Fuzz{thing='bla', blip='null'}}
And for full JSON
payload:
Root{foo=Foo{value='bar', baz='asdf'}, fuzz=Fuzz{thing='bla', blip='asdf'}}