I have an issue with deserialization of inner collection:
Imagine there are two classes:
// RootClass.java
package somepackage;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import java.util.ArrayList;
import java.util.List;
class RootClass {
public List getItems() {
return items;
}
public void setItems(List items) {
this.items = items;
}
#JsonSerialize(contentAs = Item.class)
List<Item> items = new ArrayList<>();
}
//Item.java
package somepackage;
class Item {
String name;
public Item() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Item(String cat) {
name = cat;
}
}
// main class
package somepackage;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class SampleCase {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
RootClass root = new RootClass();
root.items.add(new Item("cat"));
String json = mapper.writeValueAsString(root);
RootClass root2 = mapper.readValue(json, RootClass.class);
Item item = (Item) root2.items.get(0);
}
}
I get an exception:
Exception in thread "main" java.lang.ClassCastException: class java.util.LinkedHashMap cannot be cast to class somepackage.Item (java.util.LinkedHashMap is in module java.base of loader 'bootstrap'; somepackage.Item is in unnamed module of loader 'app')
According to javadoc #JsonSerialize(contentAs = Item.class) on the collection would have help me, but it wouldn't. The question is: What am i missing?
If it is not about this annotation i suspect there is a standard way to deal with the problem (i do not want to create custom deserializers).
Most questions on collection deserialization are about situation when root object is a collection itself, but this is not the case for me.
jackson 2.9.8
java 11.0.2 OpenJDK x64
There's nothing wrong with the basic form of your code. What you're trying to do will work. You just have problems with your code, starting with the fact that it won't even compile since you call a constructor on Item that takes a String, and yet you define no such constructor. You also need getters for Jackson to work with.
Here's a version of your code that works:
class RootClass {
List<Item> items = new ArrayList<>();
public List<Item> getItems() {
return items;
}
}
class Item {
String name;
Item() {}
Item(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public static void main(String[] argsx) throws IOException{
ObjectMapper mapper = new ObjectMapper();
RootClass root = new RootClass();
root.items.add(new Item("cat"));
RootClass root2 = null;
String json = mapper.writeValueAsString(root);
root2 = mapper.readValue(json, RootClass.class);
Item item = root2.items.get(0);
System.out.println(item.getName());
}
Output:
cat
Related
I am trying to understand Jackson and have to ask you one thing about it.
In the example below I have two classes. One of them - object to be converted to JSON, another - main method.
I know that Jackson needs default constructor but decided to experiment. I have two constructors.
So the question is - why is my output {"name":"Lion"} and int value can not be seen at all?
public class Lion extends AbstractCat {
int age;
public String name;
public Lion(int age, String name) {
this.age = age;
this.name = name;
}
public Lion() {};
}
public class Var {
public static void main(String[] args) throws IOException, JAXBException {
Lion lion = new Lion(32,"Lion");
System.out.println(converToJSON(lion));
}
public static String converToJSON(Object o) {
ObjectMapper objectMapper = new ObjectMapper();
StringWriter stringWriter = new StringWriter();
try {
objectMapper.writeValue(stringWriter,o);
} catch (IOException e) {
e.printStackTrace();
}
return stringWriter.toString();
}
}
Jackson ignores age, because it is private. Either you make age public or add a public getter method (i.e. public int getAge() { return age; }). The latter is usually the prefered variant as public fields are exposing your classes internals.
In Master class for the variable Generic<Parent> generic i am passing a Child Object in main(). During serializing i am getting correct output. But while deserializing Child Object is missing. Could anyone give suggesstions.
public class GenericSample {
public static void main(String[] args) {
Generic<Parent> generic = new Generic<Parent>();
Child child = new Child();
child.setName("I am child");
generic.setT(child);
Gson gson = new Gson();
Master master = new Master();
master.setId(2);
master.setGeneric(generic);
String valMaster = gson.toJson(master);
System.out.println(valMaster);
/*
* Output: {"id":2,"generic":{"t":{"name":"I am child"}}}
*/
Master master2 = gson.fromJson(valMaster, Master.class);
String valMaster2 = gson.toJson(master2);
System.out.println(valMaster2);
/*
* Child Object is missing
* Output: {"id":2,"generic":{"t":{}}}
*/
}
static class Master {
private int id;
private Generic<Parent> generic;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Generic<Parent> getGeneric() {
return generic;
}
public void setGeneric(Generic<Parent> generic) {
this.generic = generic;
}
}
static class Generic<T> {
T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
static class Parent {
private String type;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
static class Child extends Parent {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Problem
Gson tries to deserialize the generic value into Parent, not Child. Since there is type as null, you can see no data in the object deserialized which appears as {}. If you add child.setType("type"); then the outputs become:
valMaster1: {"id":2,"generic":{"t":{"name":"I am child","type":"type"}}}
valMaster2: {"id":2,"generic":{"t":{"type":"type"}}}
However, the field name is not present in the Parent class but the Child class and Gson simply has no idea what subclass of Parent it is (if so) and completely ignores the value, which is a correct behavior.
Solution
I find basically two choices (I use all-args constructor for sake of brevity):
Elevate the upper-bounded generic type parameter to the Master class and specify the particular Child type at the point of deserialization using com.google.gson.reflect.TypeToken and java.lang.reflect.Type:
static class Master<T extends Parent> {
private int id;
private Generic<T> generic;
/* getters, setters and all-args constructor */
}
Child child = new Child("I am child");
Generic<Parent> generic = new Generic<>(child);
Master<Parent> master = new Master<>(2, generic);
Gson gson = new Gson();
String valMaster = gson.toJson(master);
System.out.println(valMaster);
// {"id":2,"generic":{"t":{"name":"I am child"}}}
Type type = new TypeToken<Master<Child>>() {}.getType();
Master<Child> master2 = gson.fromJson(valMaster, type);
String valMaster2 = gson.toJson(master2);
System.out.println(valMaster2);
// {"id":2,"generic":{"t":{"name":"I am child"}}}
Hardcode the particular generic type Generic<Child> inside the Master class. The deserialization gets the way easier, yet the design is less flexible:
static class Master {
private int id;
private Generic<Child> generic;
/* getters, setters and all-args constructor */
}
Child child = new Child("I am child");
Generic<Child> generic = new Generic<>(child);
Master master = new Master(2, generic);
Gson gson = new Gson();
String valMaster = gson.toJson(master);
System.out.println(valMaster);
// {"id":2,"generic":{"t":{"name":"I am child"}}}
Master master2 = gson.fromJson(valMaster, Master.class);
String valMaster2 = gson.toJson(master2);
System.out.println(valMaster2);
// {"id":2,"generic":{"t":{"name":"I am child"}}}
We currently have some mixins for our data objects in order to keep annotations out of the data objects. So for example
public class SomeDataObj {
private int a;
public int getA() { return this.a; }
public void setA(final int a) { this.a = a; }
}
public interface SomeDataObjMixin {
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "A")
int getA();
#JacksonXmlElementWrapper(useWrapping = false)
#JacksonXmlProperty(localName = "A")
void setA(int a);
}
Then in our object mapper class we have
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
public class OurXmlMapper extends XmlMapper {
public OurXmlMapper(final ConfigurableCaseStrategy caseStrategy) {
setPropertyNamingStrategy(caseStrategy);
setSerializationInclusion(Include.NON_NULL);
//yadda yadda
addMixin(SomeDataObj.class, SomeDataObjMixin.class);
// etc etc
}
However, for various reasons I'd like to add a new annotation to the private field in the data object, not the getter or setter. Is there a way to accomplish this through a mixin to maintain that separation? I tried creating a basic class as a mixin (not an interface) and added the private field with the
new annotation to that. This didn't accomplish what I was looking for. Any ideas?
Using Concrete class as mixin working.
// ******************* Item class *******************
public class Item {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// ******************* ItemMixIn class *******************
#JacksonXmlRootElement(localName = "item-class")
public class ItemMixIn {
#JacksonXmlProperty(localName = "firstName")
private String name;
}
// ******************* test method *******************
public void test() throws Exception {
ObjectMapper mapper = new XmlMapper();
mapper.addMixIn(Item.class, ItemMixIn.class);
Item item = new Item();
item.setName("hemant");
String res = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(item);
System.out.println(res);
}
Output:
<item-class>
<firstName>hemant</firstName>
</item-class>
I have jackson version 2.9.5.
I'm just getting familiar with Jackson binding. However, when I'm testing setSerializationInclusion(JsonInclude.Include.NON_NULL), I found that it's not working sometimes.
Here is my code
package com.blithe.main;
import com.blithe.model.Student;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Jackson_2_NullValue {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
Student s = new Student();
String stundetString = mapper.writeValueAsString(s);
System.out.println(stundetString);
// exclude null fields
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
s.setName("ss");
stundetString = mapper.writeValueAsString(s);
System.out.println(stundetString);
}
}
and the POJO
package com.blithe.model;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
// #JsonIgnoreProperties(ignoreUnknown = true)
// exclude null fields for the whole class
// #JsonInclude(Include.NON_NULL)
public class Student {
// exclude the field whe it's empty ("")
// #JsonInclude(value=Include.NON_EMPTY)
private String name;
private Integer age;
private Date birth;
// Jackson ignores it
#JsonIgnore
private String nickName;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
the output is
{"name":null,"age":null,"birth":null}
{"name":"ss","age":null,"birth":null}
The later one should be null-value excluded, but it doesn't.
However, when I put my code this way.
package com.blithe.main;
import com.blithe.model.Student;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class Jackson_2_NullValue {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
Student s = new Student();
String stundetString = mapper.writeValueAsString(s);
System.out.println(stundetString);
// exclude null fields
// mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
s.setName("ss");
stundetString = mapper.writeValueAsString(s);
System.out.println(stundetString);
}
}
It works with the output below
{}
{"name":"ss"}
Is this normal or just some kind of bug? Do I miss anything? The only maven dependency is jackson-databind 2.7.4. Any discussion is welcomed. Thanks!
Do not change ObjectMappers settings while using it. Once mapper has been in use not all settings take effect, because of caching of serializers and deserializers.
Configure an instance once and do not change settings after first use. It is done this way for thread-safety and performance.
Update: Dead links replaced with archive.org ones
http://wiki.fasterxml.com/JacksonFAQThreadSafety
http://wiki.fasterxml.com/JacksonBestPracticesPerformance
So the point is if you are using ObjectMappers at multiple places, try not to create objects again and again. it takes the configs of first initialized.
if you keep changing on a global level it will not work.
I have the follow json.
{
foo:{
id:1
},
name:'Albert',
age: 32
}
How can I deserialize to Java Pojo
public class User {
private int fooId;
private String name;
private int age;
}
This is what you need to deserialize, using the JsonProperty annotations in your constructor.
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
public class User {
private int fooId;
private String name;
private int age;
public int getFooId() {
return fooId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public User(#JsonProperty("age") Integer age, #JsonProperty("name") String name,
#JsonProperty("foo") JsonNode foo) {
this.age = age;
this.name = name;
this.fooId = foo.path("id").asInt();
}
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
String json = "{\"foo\":{\"id\":1}, \"name\":\"Albert\", \"age\": 32}" ;
try {
User user = objectMapper.readValue(json, User.class);
System.out.print("User fooId: " + user.getFooId());
} catch (IOException e) {
e.printStackTrace();
}
}
}
Output:
User fooId: 1
Hope it helps,
Jose Luis
You can do one of the following:
Create a concrete type representing Foo:
public class Foo {
private int id;
...
}
Then in User you would have:
public class User {
private Foo foo;
...
}
Use a Map<String, Integer>:
public class User {
private Map<String, Integer> foo;
...
}
If other callers are really expecting you to have a getFooId and a setFooId, you can still provide these methods and then either delegate to Foo or the Map depending on the option you choose. Just make sure that you annotate these with #JsonIgnore since they aren't real properties.
You can use a very helpful gson google API.
First of all, create these two classes:
User class:
public class User{
Foo foo;
String name;
int age;
//getters and setters
}
Foo class:
public class Foo{
int id;
//getters and setters
}
If you have a example.json file then deserialize it as follow
Gson gson = new Gson();
User data = gson.fromJson(new BufferedReader(new FileReader(
"example.json")), new TypeToken<User>() {
}.getType());
If you have a exampleJson String then deserialize it as follow
Gson gson = new Gson();
User data = gson.fromJson(exampleJson, User.class);