Trying to use InjectableValues in Jackson deserialization to gain control over object instantiation.
My InjectableValues instance never gets called. Jackson versions 2.0.5, 2.9.1, and a few in between.
Other than that, it works correctly (reads json to java).
Here is the test code:
ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper = mapper.setSerializationInclusion(Include.NON_NULL);
mapper = mapper.setInjectableValues(injectableValues());
return mapper;
}
InjectableValues injectableValues() {
return new InjectableValues() {
#Override
public Object findInjectableValue(Object id, DeserializationContext ctx, BeanProperty prop, Object parent) {
final Class<?> clazz = prop.getType().getRawClass();
System.out.println("Inject " + clazz);
try {
return clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
}
public void testLoad(String json) throws Exception{
Object obj = createMapper().readValue(json, A.class);
System.out.println(obj);
}
Also tried setting it explicitly on the ObjectReader:
createMapper().reader(A.class)
.with(injectableValues()).readValue(json);
Related
I have a JSON:
{
"stringField" : 1234,
"booleanField": true,
"numberField": 1200.00
}
I use object mapper to deserialize the json into:-
#Data
class SomeClass {
String stringField;
boolean booleanField;
float numberField;
}
I would like the objectMapper to throw an error because, the values for String fields must be double quoted according to the json spec. How can i get objectMapper to throw an error?
You can write custom string deserializer.(i assume you are using spring)
#Configuration
public class JacksonConfiguration {
#Bean
SimpleModule jacksonDeserializerConfiguration() {
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new StdDeserializer<String>(String.class) {
#Override
public String deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
if (!parser.hasToken(JsonToken.VALUE_STRING)) {
//throw ex what do u want
throw new RuntimeException("String not include quote");
}
return StringDeserializer.instance.deserialize(parser, context);
}
});
return module;
}
}
This should fix your issue.
class SomeClass {
#JsonDeserialize(using=ForceStringDeserializer.class)
public String stringField;
public boolean booleanField;
public float numberField;
}
class ForceStringDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
if (jsonParser.getCurrentToken() != JsonToken.VALUE_STRING) {
throw deserializationContext.wrongTokenException(jsonParser, JsonToken.VALUE_STRING, "Attempted to parse Integer to String but this is forbidden");
}
return jsonParser.getValueAsString();
}
}
You just need to setup jackson objectmapper like this
JsonFactory factory = new JsonFactory();
factory.disable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
ObjectMapper mapper = new ObjectMapper(factory)
This should throw error during serialization/deserilaization
I have following code:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
What can I do to not return null?
Caller will have to deal with this being null/missing anyway so may as well specify that your method throws a JsonProcessingException and do exception handling in the caller. Then the caller can do whatever it needs for the type it needs.
Otherwise you're just handling the same issue twice.
public static <T> T jsonToObject(String json, Class<T> object) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.readerFor(object).readValue(json);
}
Considering that:
ObjectReader readerFor(Class<?> type) - Factory method for
constructing ObjectReader that will read or update instances of
specified type
<T> T readValue(String content, TypeReference valueTypeRef) - Method
to deserialize JSON content from given JSON content String.
So the method is returning an instance of the generic type Class<T>, so you can use:
public static <T> T jsonToObject(String json, Class<T> object) {
ObjectMapper mapper = new ObjectMapper();
T instance=null;
try {
instance = mapper.readerFor(object).readValue(json);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
if (instance == null) {
instance = ((Class) ((ParameterizedType) this.getClass().
getGenericSuperclass()).getActualTypeArguments()[0]).newInstance();
}
return instance;
}
Now you will get a new instance if the result is null.
I'm trying to deserialize a Json into an existing instance in my process. So it only creates a new instance if none exists. Alls objects contains an id to Identifiy them.
I used this answer: https://stackoverflow.com/a/18405958/11584969 and tried to create a custon Deserializer for this.
So far I have managed to create a custon Deserializer which checks for existing instances, but I was not able to fill the new instance or change the existing one.
My deserialize function is:
public T deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
if (node instanceof NullNode) {
return null;
}
// get id from node
String strId = node.get("id").asText();
UUID id = UUID.fromString(strId);
// search for existing instance or create it
T mObject = ...
// fill/change instance
return (T) defaultDeserializer.deserialize(jp, ctxt, mObject);
}
The object mapper creation:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.enableDefaultTyping();
objectMapper.registerModule(new Jdk8Module());
objectMapper.registerModule(new JavaTimeModule());
SimpleModule module = new SimpleModule();
module.setDeserializerModifier(new BeanDeserializerModifier() {
#Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
if (beanDesc.getBeanClass() == Table.class)
return new ModelObjectDeserializer<>(Table.class, (JsonDeserializer<Table>) deserializer);
return deserializer;
}
});
objectMapper.registerModule(module);
The code above runs without any error or exception but the instance from mObject is not filled by defaultDeserializer.deserialize(jp, ctxt, mObject);
If I don't use my custom deserializer, the created instances are filled as expected.
It is not quite an answer to the question, but my initial goal was:
'm trying to deserialize a Json into an existing instance in my process. So it only creates a new instance if none exists. Alls objects contains an id to Identifiy them.
For everyone who tries to accomplish the same, here is how I implemented it:
public class ModelInstantiator extends StdValueInstantiator {
private static final long serialVersionUID = -7760885448565898117L;
private Class<? extends ModelObject> clazz;
/**
* #param config
* #param valueType
*/
public ModelInstantiator(DeserializationConfig config, Class<? extends ModelObject> clazz) {
super(config, config.constructType(clazz));
this.clazz = clazz;
}
#Override
public boolean canCreateFromObjectWith() {
return true;
}
#Override
public Object createFromObjectWith(DeserializationContext ctxt, Object[] args) throws IOException, JsonProcessingException {
UUID id = (UUID) args[0];
// get local object
ModelObject object = ...
// if id was not found => create and add
if (object == null) {
try {
object = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new IOException(e);
}
object.setId(id);
// add to local list
...
}
return object;
}
#Override
public SettableBeanProperty[] getFromObjectArguments(DeserializationConfig config) {
CreatorProperty idProp = new CreatorProperty(new PropertyName("id"), config.constructType(UUID.class), null, null, null, null,
0, null, PropertyMetadata.STD_REQUIRED);
return new SettableBeanProperty[] { idProp };
}
}
I had to split the local and json id. Ohterwise the id in the array is null.
I am trying to convert the following gson serialization to JACKSON serialization. Please let me know what i need to change to make it work for JACKSON
public class AbstractElementAdapter
implements JsonSerializer<AbstractElement>, JsonDeserializer<AbstractElement>
{
#Override
public JsonElement serialize(AbstractElement src, Type typeOfSrc, JsonSerializationContext context) {
JsonObject result = new JsonObject();
JsonObject properties = context.serialize(src, src.getClass()).getAsJsonObject();
if (src instanceof TruncatedElement) {
result.add("type", new JsonPrimitive(((TruncatedElement) src).getClassName()));
properties.remove("className");
} else {
result.add("type", new JsonPrimitive(src.getClass().getSimpleName()));
}
result.add("properties", properties);
return result;
}
#Override
public AbstractElement deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
JsonObject jsonObject = json.getAsJsonObject();
String type = jsonObject.get("type").getAsString();
JsonElement element = jsonObject.get("properties");
try {
return context.deserialize(element, Class.forName("com.zreflect.emyed.whiteboard.model.element." + type));
} catch (ClassNotFoundException cnfe) {
throw new JsonParseException("Unknown element type: " + type, cnfe);
}
}
}
You can create a custom serializer, something like this:
public class ItemSerializer extends JsonSerializer<AbstractElement> {
#Override
public void serialize(AbstractElement src, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
if (src instanceof TruncatedElement) {
jgen.writeStringField("type",((TruncatedElement) src).getClassName());
jgen.writeObjectFieldStart("properties");
//use jgen.writeStringField();
//jgen.writeNumberField();
//etc to every one of the values,
//but skipping className
jgen.writeEndObject();
} else {
jgen.writeStringField("type", src.getClass().getSimpleName() );
//write everythin
jgen.writeObjectField("properties", src);
}
jgen.writeEndObject();
}
}
And register it with the ObjectMapper and then do the serialization:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(yourObject.class, new ItemSerializer());
mapper.registerModule(module);
String serialized = mapper.writeValueAsString(yourObject);
To the trick of skipping className, you could also want to use a custom field filter, you have a great example here:
http://www.baeldung.com/jackson-ignore-properties-on-serialization
Jackson allows you to specify serializers through annotations. For example, see the trivial example below:
#JsonSerialize(using FooToStringSerializer)
public class Foo implements Serializable {
private String bar;
public Foo(String bar) {
this.bar = bar;
}
Then, if all I wanted to see when the object was serialized was bar, I would create the serializer like so:
public class FooToStringSerializer extends JsonSerializer<Foo> {
#Override
public void serialize(final Foo value, final JsonGenerator jgen,
final SerializerProvider provider) throws IOException
{
jgen.writeObject(value.getBar());
}
For deserialization, you can create a deserializer and register it with the ObjectMapper that will be doing the deserialization.
To register a deserializer with an object mapper, do the below:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new FooDeserializer());
mapper.registerModule(module);
For a really easy to follow example of custom deserialization, see this link:
http://www.baeldung.com/jackson-deserialization
Now i'm working with Jackson and i have some questions about it.
First of all. I have two services, first is data collecting and sending service and second receive this data and, for example, log it into a file.
So, first service has class hierarchy like this:
+----ConcreteC
|
Base ----+----ConcreteA
|
+----ConcreteB
And second service has class hierarchy like this:
ConcreteAAdapter extends ConcreteA implements Adapter {}
ConcreteBAdapter extends ConcreteB implements Adapter {}
ConcreteCAdapter extends ConcreteC implements Adapter {}
The first service knows nothing about ConcreteXAdapter.
The way i'm sending the data on the first service:
Collection<Base> data = new LinkedBlockingQueue<Base>()
JacksonUtils utils = new JacksonUtils();
data.add(new ConcreteA());
data.add(new ConcreteB());
data.add(new ConcreteC());
...
send(utils.marshall(data));
...
public class JacksonUtils {
public byte[] marshall(Collection<Base> data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream() {
#Override
public byte[] toByteArray() {
return buf;
}
};
getObjectMapper().writeValue(out, data);
return out.toByteArray();
}
protected ObjectMapper getObjectMapper() {
return new ObjectMapper();
}
public Object unmarshall(byte[] json) throws IOException {
return getObjectMapper().readValue(json, Object.class);
}
public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
return getObjectMapper().readValue(source, typeReference);
}
public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
return getObjectMapper().readValue(json, typeReference);
}
}
So, i want to desirialize json into Collection of ConcreteXAdapter, not into Collection of ConcreteX (ConcreteA -> ConcreteAAdapter, ConcreteB -> ConcreteBAdapter, ConcreteC -> ConcreteCAdapter). In the case i described i want to get:
Collection [ConcreteAAdapter, ConcreteBAdapter, ConcreteCAdapter]
How can i do this?
For this purpose you need to pass additional info in JSON:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.PROPERTY, property="#type")
class Base {
...
}
Then on serialization it will add #type field:
objectMapper.registerSubtypes(
new NamedType(ConcreteAAdapter.class, "ConcreteA"),
new NamedType(ConcreteBAdapter.class, "ConcreteB"),
new NamedType(ConcreteCAdapter.class, "ConcreteC")
);
// note, that for lists you need to pass TypeReference explicitly
objectMapper.writerWithType(new TypeReference<List<Base>>() {})
.writeValueAsString(someList);
{
"#type" : "ConcreteA",
...
}
on deserialization it will be:
objectMapper.registerSubtypes(
new NamedType(ConcreteA.class, "ConcreteA"),
new NamedType(ConcreteB.class, "ConcreteB"),
new NamedType(ConcreteC.class, "ConcreteC")
);
objectMapper.readValue(....)
More info here
How I solved this problem. Here is a class diagram for an example project:
So i want to get the ConcreteAAdapter form ConcreteA after deserialization.
My solution is to extend ClassNameIdResolver to add functionality to deserialize base class objects into subtype class objects (subtype classes adds no extra functionality and additional fields).
Here is a code which creates ObjectMapper for deserialization:
protected ObjectMapper getObjectMapperForDeserialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
{
put(ConcreteA.class, ConcreteAAdapter.class);
put(ConcreteB.class, ConcreteBAdapter.class);
put(ConcreteC.class, ConcreteCAdapter.class);
}
};
#Override
public String idFromValue(Object value) {
return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
}
#Override
public JavaType typeFromId(String id) {
try {
return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
} catch (ClassNotFoundException e) {
// todo catch the e
}
return super.typeFromId(id);
}
});
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
And here is a code which create ObjectMapper for serialization:
protected ObjectMapper getObjectMapperForSerialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
Test code:
public static void main(String[] args) throws IOException {
JacksonUtils JacksonUtils = new JacksonUtilsImpl();
Collection<Base> data = new LinkedBlockingQueue<Base>();
data.add(new ConcreteA());
data.add(new ConcreteB());
data.add(new ConcreteC());
String json = JacksonUtils.marshallIntoString(data);
System.out.println(json);
Collection<? extends Adapter> adapters = JacksonUtils.unmarshall(json, new TypeReference<ArrayList<Adapter>>() {});
for (Adapter adapter : adapters) {
System.out.println(adapter.getClass().getName());
}
}
Full code of JacksonUtils class:
public class JacksonUtilsImpl implements JacksonUtils {
#Override
public byte[] marshall(Collection<Base> data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream() {
#Override
public byte[] toByteArray() {
return buf;
}
};
getObjectMapperForSerialization().writerWithType(new TypeReference<Collection<Base>>() {}).writeValue(out, data);
return out.toByteArray();
}
#Override
public String marshallIntoString(Collection<Base> data) throws IOException {
return getObjectMapperForSerialization().writeValueAsString(data);
}
protected ObjectMapper getObjectMapperForSerialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()));
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
protected ObjectMapper getObjectMapperForDeserialization() {
ObjectMapper mapper = new ObjectMapper();
StdTypeResolverBuilder typeResolverBuilder = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.OBJECT_AND_NON_CONCRETE);
typeResolverBuilder = typeResolverBuilder.inclusion(JsonTypeInfo.As.PROPERTY);
typeResolverBuilder.init(JsonTypeInfo.Id.CLASS, new ClassNameIdResolver(SimpleType.construct(Base.class), TypeFactory.defaultInstance()) {
private HashMap<Class, Class> classes = new HashMap<Class, Class>() {
{
put(ConcreteA.class, ConcreteAAdapter.class);
put(ConcreteB.class, ConcreteBAdapter.class);
put(ConcreteC.class, ConcreteCAdapter.class);
}
};
#Override
public String idFromValue(Object value) {
return (classes.containsKey(value.getClass())) ? value.getClass().getName() : null;
}
#Override
public JavaType typeFromId(String id) {
try {
return classes.get(Class.forName(id)) == null ? super.typeFromId(id) : _typeFactory.constructSpecializedType(_baseType, classes.get(Class.forName(id)));
} catch (ClassNotFoundException e) {
// todo catch the e
}
return super.typeFromId(id);
}
});
mapper.setDefaultTyping(typeResolverBuilder);
return mapper;
}
#Override
public Object unmarshall(byte[] json) throws IOException {
return getObjectMapperForDeserialization().readValue(json, Object.class);
}
#Override
public <T> T unmarshall(InputStream source, TypeReference<T> typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(source, typeReference);
}
#Override
public <T> T unmarshall(byte[] json, TypeReference<T> typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(json, typeReference);
}
#Override
public <T> Collection<? extends T> unmarshall(String json, Class<? extends Collection<? extends T>> klass) throws IOException {
return getObjectMapperForDeserialization().readValue(json, klass);
}
#Override
public <T> Collection<? extends T> unmarshall(String json, TypeReference typeReference) throws IOException {
return getObjectMapperForDeserialization().readValue(json, typeReference);
}
}
I find programmerbruce's approach to be the most clear and easy to get working (example below).
I got the information from his answer to a related question:
https://stackoverflow.com/a/6339600/1148030
and the related blog post:
http://programmerbruce.blogspot.fi/2011/05/deserialize-json-with-jackson-into.html
Also check out this friendly wiki page (also mentioned in Eugene Retunsky's answer):
https://github.com/FasterXML/jackson-docs/wiki/JacksonPolymorphicDeserialization
Another nice wiki page: https://github.com/FasterXML/jackson-docs/wiki/JacksonMixInAnnotations
Here is a short example to give you the idea:
Configure the ObjectMapper like this:
mapper.getDeserializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
mapper.getSerializationConfig().addMixInAnnotations(Base.class, BaseMixin.class);
Example BaseMixin class (easy to define as an inner class.)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#JsonSubTypes.Type(value=ConcreteA.class, name="ConcreteA"),
#JsonSubTypes.Type(value=ConcreteB.class, name="ConcreteB")
})
private static class BaseMixin {
}
On second service you could define the BaseMixin like this:
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="type")
#JsonSubTypes({
#JsonSubTypes.Type(value=ConcreteAAdapter.class, name="ConcreteA"),
#JsonSubTypes.Type(value=ConcreteBAdapter.class, name="ConcreteB")
})
private static class BaseMixin {
}