I try to have Jackson property type info serialized, even when my type is referenced by a map.
With this simple sample:
import java.util.HashMap;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class DemoJackson {
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public static abstract class Animal {}
#JsonTypeName("cat")
public static class Cat extends Animal {}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Cat cat = new Cat();
System.out.println(objectMapper.writeValueAsString(cat));
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("data", cat);
System.out.println(objectMapper.writeValueAsString(map));
}
}
I get:
{"#type":"cat"}
{"data":{}}
But I would like to have:
{"#type":"cat"}
{"data":{"#type":"cat"}}
Is it possible? How to do it?
I have tried with enableDefaultTyping but I get:
{"#type":"cat"}
{"data":["DemoJackson$Cat",{}]}
When you are serializing a map directly, as you do, the object mapper needs the type information about the map contents because of generic typing. Please refer to chapter 5.1 of Polymorphic Type Handling wiki page.
An example of passing the type reference information when serializing the map of Animals.
objectMapper.writerWithType(new TypeReference<Map<String, Animal>>() {})
.writeValueAsString(map)
Output:
{"#type":"cat"}
{"data":{"#type":"cat"}}
Related
I'm getting the following exception when trying to deserialize with ObjectMapper onto a parameterized class (works fine for non-parametrized classes):
java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class com.xyz.A (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; com.xyz.A is in unnamed module of loader 'app')
Here's the original code:
Foo<A> request = OBJECT_MAPPER.readValue(payload, Foo.class);
I tried:
Foo<A> request = OBJECT_MAPPER.readValue(payload, new TypeReference<Foo<A>>() {});
As well as:
JavaType myType = OBJECT_MAPPER.getTypeFactory()
.constructParametricType(Foo.class, A.class);
Foo<A> request = OBJECT_MAPPER.readValue(payload, myType);
But I still get the same exception.
Could there be something special about my scenario that's not covered in these questions?
Jackson is not deserialising a generic list that it has serialised
Jackson and generic type reference
Jackson - Deserialize using generic class
One thing I can think of is that my Foo is actually an #AutoMatter-annotated interface that generates the class:
#AutoMatter
public interface Foo<T> {
Optional<T> parent;
Optional<List<T>> children;
}
Normally we have no issues mapping onto AutoMatter-generated classes though. It's just adding the parametrization <T> that seems to be causing issues.
Does anyone have an idea?
Edit to answer #MichalZiober's questions:
In my test code I'm actually just serializing what I know is a valid object, i.e. then deserializing that to get back the object I started with:
Foo<A> myExampleObject;
ByteString.encodeUtf8(OBJECT_MAPPER.writeValueAsString(myExampleObject));
Edit 2
Okay, so it looks like we are already importing that module:
#VisibleForTesting
public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.registerModule(new JodaModule())
.registerModule(new GuavaModule())
.registerModule(new AutoMatterModule())
.registerModule(new Jdk8Module())
.registerModule(new ProtobufModule())
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
When you use Optional in POJO structure you need to enable Jdk8Module from jackson-modules-java8. Below example shows that with this module registered we can serialise and deserialise data:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.norberg.automatter.AutoMatter;
import io.norberg.automatter.jackson.AutoMatterModule;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class JsonApp {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.registerModule(new AutoMatterModule());
mapper.registerModule(new Jdk8Module());
String json = "{\"parent\":\"Aaaa\", \"children\":[\"a\"]}";
System.out.println(mapper.readValue(json, Foo.class));
Foo<StringWrapper> foo = new FooBuilder<StringWrapper>()
.parent(new StringWrapperBuilder().value("PARENT").build())
.children(Arrays.asList(new StringWrapperBuilder().value("CHILD1").build()))
.build();
json = mapper.writeValueAsString(foo);
System.out.println(json);
System.out.println(mapper.readValue(json, Foo.class));
}
}
#AutoMatter
interface Foo<T> {
Optional<T> parent();
Optional<List<T>> children();
}
#AutoMatter
interface StringWrapper {
String value();
}
Above code prints:
Foo{parent=Optional[Aaaa], children=Optional[[a]]}
{
"parent" : {
"value" : "PARENT"
},
"children" : [ {
"value" : "CHILD1"
} ]
}
Foo{parent=Optional[{value=PARENT}], children=Optional[[{value=CHILD1}]]}
For now I just had to abandon parameterizing. I only had so many classes,
Foo<A>
Foo<B>
Foo<C>
It was easier to just create each one explicitly:
#AutoMatter
public interface FooA {
A parent;
List<A> children;
}
#AutoMatter
public interface FooB {
B parent;
List<B> children;
}
#AutoMatter
public interface FooC {
C parent;
List<C> children;
}
(I've also realized the Optionals were unnecessary.)
Not really an answer, so not accepting it.
I try to serialize some data with jackson which works pretty well for most cases but now I have an issue with a list. The list is of type A which is an abstract class and may contain circular dependencies. I can't figure out how to serialize this construct with jackson. The combination of identityInformation and typeInformation doesn't seem to properly work.
Below is Examplecode which produces the issue I am facing.
I am using Jackson version 2.8.3. Am I missing something? Is there a good solution to serialize and deserialize this kind of list?
Thanks in advance for any help!
Code:
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
public static abstract class A {
public A member;
}
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static class B extends A {
}
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
ObjectMapper mapper = new ObjectMapper();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
try{
String serialized = mapper.writerFor(listType).writeValueAsString(list);
System.out.println(serialized);
list = mapper.readValue(serialized, listType);
} catch (Exception e){
e.printStackTrace();
}
}
}
Generated json String:
[{"#class":"JacksonTest$B","#id":1,"member":{"#class":"JacksonTest$B","#id":2,"member":null}},2]
Error when trying to read the String:
com.fasterxml.jackson.databind.JsonMappingException: Unexpected token (VALUE_NUMBER_INT), expected FIELD_NAME: missing property '#class' that is to contain type id (for class JacksonTest$A)
It seems like jackson expects the second entry to also be a json object containing the #class field and doesn't recognize that the second array element is a reference to an already existing object.
Type of field "A.member" is "A" and you missed out #JsonIdentityInfo for "A" type. Due to this, Jackson consider the object from list is a normal object( only id was serialized) and due to lack of identityinfo, jackson is not able to apply the object reference lookup mechanism during de-serializing and hence you see the error.
To fix your issue you need to add "#JsonIdentityInfo for type "A" too or better you can move #JsonIdentityInfo declaration from type "B" to "A".
code:
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.hamcrest.core.IsSame;
import org.junit.Test;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
public class JacksonTest {
#JsonTypeInfo(
use = JsonTypeInfo.Id.CLASS,
include = JsonTypeInfo.As.PROPERTY,
property = "#class")
#JsonSubTypes({ #JsonSubTypes.Type(value = B.class) })
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "#id")
public static abstract class A {
public A member;
}
public static class B extends A {
}
#Test
public void testCirularPolymorphicSerialization() throws Exception {
ObjectMapper mapper = new ObjectMapper();
// set up fixture
List<A> list = new ArrayList<A>();
B instance1 = new B();
B instance2 = new B();
instance1.member = instance2;
list.add(instance1);
list.add(instance2);
CollectionType listType = mapper.getTypeFactory().constructCollectionType(list.getClass(), A.class);
String serialized = mapper.writerFor(listType).writeValueAsString(list);
list = mapper.readValue(serialized, listType);
assertThat(list.size(), is(2));
assertThat(list.get(0).member, sameInstance(list.get(1)));
}
}
I have a such model:
public class Type {
#XmlElementRef(name = "ConversationId", namespace = "...", type = JAXBElement.class, required = false)
protected JAXBElement<String> conversationId;
}
and part of json:
"conversationId": {
"declaredType": "java.lang.String",
"globalScope": false,
...,
"value": "ABC000000001"
},
I tried use this mapper for deserialization:
public static interface JAXBElementMixin {
#JsonValue
Object getValue();
}
ObjectMapper mapper = new ObjectMapper();
JaxbAnnotationIntrospector jaxbAnnotationIntrospector=new JaxbAnnotationIntrospector(mapper.getTypeFactory());
JacksonAnnotationIntrospector jacksonAnnotationIntrospector=new JacksonAnnotationIntrospector();
mapper.setAnnotationIntrospector(AnnotationIntrospector.pair(jaxbAnnotationIntrospector, jacksonAnnotationIntrospector));
mapper.addMixIn(JAXBElement.class, JAXBElementMixin.class);
But in any case, my program doesn't work well I got:
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class javax.xml.bind.JAXBElement]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
I wanted to read some docs about mixins, but there is outdated information on the offcial jackson's website, I use 2.5.1 version
I had the same issue and finally was able to figure it out. Two things need to happen in order for this to work:
The Mixin class MUST be in the separate file, not an inner or
anonymous class.
The Mixin class MUST be in a different package,
i.e. if the code which creates the mapper is in the package
com.foo.bar then Mixin class MUST NOT be in this package.
Here's my code:
package com.foo.bar;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import javax.xml.bind.JAXBElement;
import javax.xml.namespace.QName;
#JsonIgnoreProperties(value = {"globalScope", "typeSubstituted", "nil", "scope"})
public abstract class JAXBElementMixIn<T> extends JAXBElement<T> {
#JsonCreator
public JAXBElementMixIn(#JsonProperty("name") QName name,
#JsonProperty("declaredType") Class<T> declaredType,
#JsonProperty("value") T value) {
super(name, declaredType, value);
}
}
and the code which uses it:
package x.y.z;
...
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(JAXBElement.class, JAXBElementMixIn.class);
I have a a Map<String,Foo> foosMap that I want to serialize through Jackson . Now I want following two settings on the serialization process:
The Map can have have plenty of null values and null keys and I don't want nulls to be serialized.
For all those Foos that are getting serialized, I do not want to serialize null objects referenced inside Foo.
What is the best way to achieve this ? I am using jackson-core1.9 and jackson-mapper1.9 jars in my project.
If it's reasonable to alter the original Map data structure to be serialized to better represent the actual value wanted to be serialized, that's probably a decent approach, which would possibly reduce the amount of Jackson configuration necessary. For example, just remove the null key entries, if possible, before calling Jackson. That said...
To suppress serializing Map entries with null values:
Before Jackson 2.9
you can still make use of WRITE_NULL_MAP_VALUES, but note that it's moved to SerializationFeature:
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
Since Jackson 2.9
The WRITE_NULL_MAP_VALUES is deprecated, you can use the below equivalent:
mapper.setDefaultPropertyInclusion(
JsonInclude.Value.construct(Include.ALWAYS, Include.NON_NULL))
To suppress serializing properties with null values, you can configure the ObjectMapper directly, or make use of the #JsonInclude annotation:
mapper.setSerializationInclusion(Include.NON_NULL);
or:
#JsonInclude(Include.NON_NULL)
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To handle null Map keys, some custom serialization is necessary, as best I understand.
A simple approach to serialize null keys as empty strings (including complete examples of the two previously mentioned configurations):
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.SerializerProvider;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Map<String, Foo> foos = new HashMap<String, Foo>();
foos.put("foo1", new Foo("foo1"));
foos.put("foo2", new Foo(null));
foos.put("foo3", null);
foos.put(null, new Foo("foo4"));
// System.out.println(new ObjectMapper().writeValueAsString(foos));
// Exception: Null key for a Map not allowed in JSON (use a converting NullKeySerializer?)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false);
mapper.setSerializationInclusion(Include.NON_NULL);
mapper.getSerializerProvider().setNullKeySerializer(new MyNullKeySerializer());
System.out.println(mapper.writeValueAsString(foos));
// output:
// {"":{"bar":"foo4"},"foo2":{},"foo1":{"bar":"foo1"}}
}
}
class MyNullKeySerializer extends JsonSerializer<Object>
{
#Override
public void serialize(Object nullKey, JsonGenerator jsonGenerator, SerializerProvider unused)
throws IOException, JsonProcessingException
{
jsonGenerator.writeFieldName("");
}
}
class Foo
{
public String bar;
Foo(String bar)
{
this.bar = bar;
}
}
To suppress serializing Map entries with null keys, further custom serialization processing would be necessary.
For Jackson versions < 2.0 use this annotation on the class being serialized:
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
Answer seems to be a little old, What I did was to use this mapper to convert a MAP
ObjectMapper mapper = new ObjectMapper().configure(SerializationConfig.Feature.WRITE_NULL_MAP_VALUES, false);
a simple Map:
Map<String, Object> user = new HashMap<String,Object>();
user.put( "id", teklif.getAccount().getId() );
user.put( "fname", teklif.getAccount().getFname());
user.put( "lname", teklif.getAccount().getLname());
user.put( "email", teklif.getAccount().getEmail());
user.put( "test", null);
Use it like this for example:
String json = mapper.writeValueAsString(user);
my solution, hope help
custom ObjectMapper and config to spring xml(register message conveters)
public class PyResponseConfigObjectMapper extends ObjectMapper {
public PyResponseConfigObjectMapper() {
disable(SerializationFeature.WRITE_NULL_MAP_VALUES); //map no_null
setSerializationInclusion(JsonInclude.Include.NON_NULL); // bean no_null
}
}
I have a class having trivial string typed fields and one map only:
class MyClass {
#SerializedName("handle");
String nickName;
Map randomDetails;
}
My requirement is to create a map of fieldName to fieldValue (Map) but the fieldNames should be the same as #SerializedName rather than Myclass's field name. I realize that for a complex type like MyClass I may have to do some low-level deserialization myself. Has anyone come across this?
If you use a library, you shouldn't need to do any low-level work.
I haven't used it (yet) but Jackson looks like it'll do what you need.
It would be especially easy if you're not required to use that #SerializedName annotation, as Jackson provides a suite of its own annotations which do exactly what you need - (see the #JsonProperty annotation).
If you use the Jackson Tree Model mode of operation, you should get something like the map-based results you're looking for.
(I think I understand that the question concerns how to use Gson to deserialize a JSON map structure to a Java Map.)
Gson currently needs a little bit more type information about the Map than the Java class structure in the original question provides. Instead of declaring that randomDetails is a plain old Map, let Gson know that it's a Map<String, String>. Then, the following example JSON and simple deserialization code runs as expected.
input.json Contents:
{
"handle":"the handle",
"random_details":{"one":1,"too":"B","3":false,"for":5.32}
}
Foo.java:
import java.io.FileReader;
import java.util.Map;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
public class Foo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
Gson gson = gsonBuilder.create();
MyClass myObject = gson.fromJson(new FileReader("input.json"), MyClass.class);
System.out.println(gson.toJson(myObject));
}
}
class MyClass
{
#SerializedName("handle")
String nickName;
Map<String, String> randomDetails;
}
Note that this converts all values in the Map into Strings. If you wanted something more generic, like a Map<String, Object>, or if randomDetails must be a plain old Map without additional type information, then it's necessary to implement custom deserialization processing, as described in the user guide. (This is a situation where Gson unfortunately does not currently automatically generate Java values of String or primitive type from JSON primitives, if the declared Java type is simply Object. Thus it's necessary to implement the custom deserialization.)
Here's one such example.
import java.io.FileReader;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;
public class Foo
{
public static void main(String[] args) throws Exception
{
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
gsonBuilder.registerTypeAdapter(MyClass.class, new MyClassDeserializer());
Gson gson = gsonBuilder.create();
MyClass myObject = gson.fromJson(new FileReader("input.json"), MyClass.class);
System.out.println(gson.toJson(myObject));
}
}
class MyClassDeserializer implements JsonDeserializer<MyClass>
{
#Override
public MyClass deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException
{
JsonObject object = json.getAsJsonObject();
String nickName = object.get("handle").getAsString();
Set<Map.Entry<String, JsonElement>> mapEntries = object.get("random_details").getAsJsonObject().entrySet();
Map randomDetails = new HashMap(mapEntries.size());
for (Map.Entry<String, JsonElement> mapEntry : mapEntries)
{
String key = mapEntry.getKey();
Object value;
JsonPrimitive jsonPrimitive = mapEntry.getValue().getAsJsonPrimitive();
if (jsonPrimitive.isNumber()) value = jsonPrimitive.getAsNumber();
else if (jsonPrimitive.isBoolean()) value = jsonPrimitive.getAsBoolean();
else value = jsonPrimitive.getAsString();
randomDetails.put(key, value);
}
MyClass myObject = new MyClass();
myObject.nickName = nickName;
myObject.randomDetails = randomDetails;
return myObject;
}
}
class MyClass
{
#SerializedName("handle")
String nickName;
Map randomDetails;
}