Spring boot, Jackson Json Issue while serializing and deserializing - java

For some use case, I need to convert one POJO to another POJO with the different fields name. I tried using Jackson object mapper. It worked in some extends. However end result is not what I expected.
public class JacksonTest {
public static void main(String[] args) throws IOException{
ObjectMapper mapper = new ObjectMapper();
User user = new User("Deepak", "111", "Singapore");
UserMap newUser = mapper.convertValue(user, UserMap.class);
System.out.println("SOUT: " + newUser);
System.out.println("Jackson: " + mapper.writeValueAsString(newUser));
}
}
class User {
User(String name, String id, String address){
this.name = name;
this.id = id;
this.address = address;
}
String name;
String id;
String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
class UserMap implements Serializable {
#JsonProperty("name")
String name;
private Map<String, Object> meta = new HashMap<>();
#JsonAnyGetter
public Map<String, Object> any() {
return meta;
}
#JsonAnySetter
public void set(String name, Object value) {
meta.put(name, value);
}
#Override
public String toString() {
return "UserMap{" +
"name_new='" + name + '\'' +
", meta=" + meta.keySet().stream().map(x-> x+ ":: "+ meta.get(x)).collect(Collectors.joining(", ")) +
'}';
}
}
If you run, the output would be :
SOUT: UserMap{name_new='Deepak', meta=address:: Singapore, id:: 111}
Jackson: {"name":"Deepak","address":"Singapore","id":"111"}
I am using Springboot which internally uses jackson serializer. It converts the newUser object to normal user class again. I want to serialize string in the way class constructed. I want the output in SOUT format.

I think you misunderstood what the #JsonAnyGetter/#JsonAnySetter pair will, in effect, do.
It allows you to create a almost dynamic bean, with mandatory as well as voluntary fields. In your case, the name would be mandatory, and all other fields voluntary.
What goes on under the hood is not that your UserMap gets converted to a User. What you see is a serialized UserMap, but since it has the same fields and values as the corresponding User instance, their serialized forms look identical.

I couldn't get the auto serialization and deserialization to work using the default Spring boot beans. In the end, this worked well for me after including Project Lombok and apache BeanUtils:
#ToString() #Getter() #Setter() #NoArgsConstructor()
public class User {
private String email;
private String bio;
private String image;
private String displayName;
private String userId;
private long lat;
private long lng;
public User(String json) throws JsonParseException, JsonMappingException, IOException, IllegalAccessException, InvocationTargetException {
ObjectMapper om = new ObjectMapper();
User u = om.readValue(json, User.class);
BeanUtils.copyProperties(this, u);
}
}
http://commons.apache.org/proper/commons-beanutils/download_beanutils.cgi
https://projectlombok.org/

Related

How to deserialize JSON with #JsonCreator and #JsonGetter

I have the JSON looks like the following:
{
"name":"John",
"n_age":500
}
and I have a class Person:
public class Person {
private final String name;
private final int age;
#JsonCreator
public Person(#JsonProperty("name") String name) {
this.name = name;
this.age = 100;
}
public String getName() {
return name;
}
#JsonGetter("n_age")
public int getAge() {
return age;
}
}
I need to deserialize and serialize it, but when I'm trying to deserialize this JSON I get unexpected result.
public static void main(String... args) {
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(args[0], Person.class);
System.out.println(person.getAge()); // I got 500, but I expect 100.
}
Why when I'm trying to deserialize it the #JsonGetter annotation is used for it?
How can I disable #JsonGetter annotation when I try to deserialize the JSON?
If #JsonGetter is used as is currently, it will map property n_age to field age. To citate the docs - It can be used as an alternative to more general JsonProperty annotation (which is the recommended choice in general case).
To fix this behaviour, you need to:
Tell jackson to ignore property n_age, otherwise you will get exception for unrecognized property not marked as ignorable - #JsonIgnoreProperties("n_age").
Tell jackson to allow getters for ignored properties(basically make it readonly) - #JsonIgnoreProperties(value = {"n_age"}, allowGetters = true)
In the end, Person should look like this:
#JsonIgnoreProperties(value = {"n_age"}, allowGetters = true)
public class Person {
private final String name;
private final int age;
#JsonCreator
public Person(#JsonProperty("name") String name) {
this.name = name;
this.age = 100;
}
public String getName() {
return name;
}
#JsonGetter("n_age")
public int getAge() {
return age;
}
#Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
I found the solution for fixing my issue, maybe it's a bad way, but it works for me as well. I'm ignoring the n_age property during deserialization and allowing getters during serialization.
Thanks a lot #Chaosfire for the help!
#JsonIgnoreProperties({"n_age"}, allowGetters = true)
public class Person {
private final String name;
private final int age;
#JsonCreator
public Person(#JsonProperty("name") String name) {
this.name = name;
this.age = 100;
}
public String getName() {
return name;
}
#JsonGetter("n_age")
public int getAge() {
return age;
}
}

Add filter method for all setter methods in java

I have the following class, with all the getter-setter method.
public class Employee {
#JsonProperty("FirstName")
private String FirstName;
#JsonProperty("LastName")
private String LastName;
#JsonProperty("MiddleName")
private String MiddleName;
#JsonProperty("Address")
private String Address;
public String getMiddleName() {
return MiddleName;
}
public void setMiddleName(String middleName) {
MiddleName = middleName;
}
public String getLastName() {
return LastName;
}
public void setLastName(String lastName) {
LastName = lastName;
}
public String getFirstName() {
return FirstName;
}
public void setFirstName(String firstName) {
FirstName = firstName;
}
public String getAddress() {
return Address;
}
public void setAddress(String address) {
Address = address;
}
}
Also, I have below JSON string.
String jsonString = "{\"employee\": {\"FirstName\": \"FirstName++bb####**\",\"LastName\": \"LastName++bb####\",\"MiddleName\": \"MiddleName++bb####\",\"Address\": \"Address++bb####\"}}"
By using jackson API I am converting json string to java object. Here is my code
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(jsonString, Employee.new);
Now I have to add a filter while mapping my JSON string to a java object, such that it will remove my special characters from all the fields.
We can achieve by updating setter methods. Ex.
public void setAddress(String address) {
Address = address.replaceAll("[-+.^:,#,*,(,)]","");
}
Is there any better way to add a filter in Jackson API or any other way to achieve this without updating setter method.
You can create your own custom deserializer, and can write your required logic in that deserializer. Here is how you can write your own deserializer - https://www.baeldung.com/jackson-deserialization
Snippet of how you can create custom deserializer:
public class MyDeserializer extends StdDeserializer<String> {
public MyDeserializer() {
this(null);
}
public MyDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Item deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
String val = jp.getValueAsString();
return val.replaceAll("[-+.^:,#,*,(,)]","");
}
}
You can annotate your fields with your custom deserializer class like this:
#JsonDeserialize(using = MyDeserializer.class)
private String address;
Then whenever JSON object will be deserialized to Employee class, logic in MyDeserializer will be used.
You could do it in three steps.
Parse string to JsonNode' (JsonNode jsonNode = mapper.readTree(jsonString);`)
Iterate thru attributes and fix values.
Iterator<String> elements = ((ObjectNode) jsonNode).fieldNames();
while (elements.hasNext()){
String name = elements.next();
String oldText = jsonNode.get(name).asText();
String newText = transform(oldText);
((ObjectNode) jsonNode).set(name, new TextNode(newText));
}
Finally convert to DTO (mapper.convertValue(jsonNode, Employee.class);)
I don't wanna duplicate the content which already exists here Serialize Only Fields that meet a Custom Criteria with Jackson. The only thing we need to implement the filter logic.

Jackson object mapper returns 0 for integer and null for string

I have a list of structures that I will be getting it from restEndpoint and I have to map it to list of Java Object. But in this project I had just given a string in json format as input. I am always getting null when I try to fetch the string data in my object and 0 when I fetch integer.
I think the objectMapper is not able to map the string json to object. But there is no error that I'm getting.
results = mapper.readValue(json, new TypeReference>() {}); None worked. Maybe some configuration or version issue?>
My Object
public class myObject {
#JsonProperty("userID")
private int userID;
#JsonProperty("id")
private int id;
#JsonProperty("title")
private String title;
#JsonProperty("body")
private String body;
#JsonCreator
public myObject() {
}
#JsonProperty("userID")
public int getUserId() {
return userID;
}
#JsonProperty("userID")
public void setUserId(int userId) {
this.userID = userId;
}
#JsonProperty("id")
public int getId() {
return id;
}
#JsonProperty("id")
public void setId(int id) {
this.id = id;
}
#JsonProperty("title")
public String getTitle() {
return title;
}
#JsonProperty("title")
public void setTitle(String title) {
this.title = title;
}
#JsonProperty("body")
public String getBody() {
return body;
}
#JsonProperty("body")
public void setBody(String body) {
this.body = body;
}
My mapper function is something like this
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(mapper.getSerializationConfig().getDefaultVisibilityChecker()
.withFieldVisibility(JsonAutoDetect.Visibility.ANY)
.withGetterVisibility(JsonAutoDetect.Visibility.NONE)
.withSetterVisibility(JsonAutoDetect.Visibility.NONE)
.withCreatorVisibility(JsonAutoDetect.Visibility.NONE));
try {
//List<myObject> mstCodes = null;
String json = "[{\"userID\":1,\"id\":\"1\",\"title\":\"IT\",\"body\":\"123234\"},{\"userID\":0,\"id\":\"2\",\"title\":\"Accounting\",\"body\":\"adsfnsdf\"}]";
List<myObject> mstCodes = mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, myObject.class));
System.out.println(mstCodes.size());
System.out.println(mstCodes.get(0));
System.out.println(mstCodes.get(0).getUserId());
System.out.println(mstCodes.get(0).getBody());
} catch (IOException e) {
System.out.println("Failed serializing response" + e.getMessage());
}
The outputs that I receive for above print statements are:
2
com.example.varun.testProject$myObject#4e7dc304
0
null
It might be a simple mistake, but any help is appreciated. Thanks.
I just simplified your myObject class with Lombok for demo purpose.
I also just used a simple ObjectMapper.
The trick is to use a TypeReference which takes a generic type that can be whatever you need (List<MyObject> in your case).
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.io.IOException;
import java.util.List;
#Getter
#Builder
#NoArgsConstructor
#AllArgsConstructor
class MyObject {
#JsonProperty("userID")
private int userID;
#JsonProperty("id")
private int id;
#JsonProperty("title")
private String title;
#JsonProperty("body")
private String body;
}
public class Demo {
public static void main(String[] args) {
final String json = "[{\"userID\":1,\"id\":\"1\",\"title\":\"IT\",\"body\":\"123234\"},{\"userID\":0,\"id\":\"2\",\"title\":\"Accounting\",\"body\":\"adsfnsdf\"}]";
final ObjectMapper objectMapper = new ObjectMapper();
try {
final List<MyObject> results = objectMapper.readValue(json, new TypeReference<List<MyObject>>() {});
System.out.println(results.size());
System.out.println(results.get(0).getUserID());
System.out.println(results.get(0).getBody());
} catch (IOException e) {
e.printStackTrace();
}
}
}
The output is:
2
1
123234
EDIT:
To solve the error No suitable constructor found for type, here is how your object should look like without Lombok (but it does the same). You must provide an empty constructor and an all args constructor.
#Getter
class MyObject {
#JsonProperty("userID")
private int userID;
#JsonProperty("id")
private int id;
#JsonProperty("title")
private String title;
#JsonProperty("body")
private String body;
public MyObject() {}
public MyObject(int userID, int id, String title, String body) {}
}
You should use the default object mapper below:
ObjectMapper objectMapper = new ObjectMapper();
and then use this objectMapper for achieving what you desire.
For a single object, you can use following code snippet for mapping JSON to your object with JACKSON.
YourObject yourObject = (YourObject) mapper.readValue(json, YourObject.class);
Finally, because of that you have asked: The ObjectMapper is able to map the JSON to Objects with JACKSON with no errors.
This is how it worked using plain ObjectMapper without redundant annotations on MyObject (Jackson 2.10):
public class MyObject {
private int userID;
private int id;
private String title;
private String body;
public int getUserID() { return userID; }
public void setUserID(int userID) { this.userID = userID; }
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getBody() { return body; }
public void setBody(String body) { this.body = body; }
#Override
public String toString() {
return "MyObject [userID=" + userID + ", id=" + id + ", title=" + title + ", body=" + body + "]";
}
}
public class MyObjectJsonTest {
public static void main(String...args) throws JsonMappingException, JsonProcessingException {
String json = "[{\"userID\":1,\"id\":\"1\",\"title\":\"IT\",\"body\":\"123234\"},{\"userID\":0,\"id\":\"2\",\"title\":\"Accounting\",\"body\":\"adsfnsdf\"}]";
ObjectMapper mapper = new ObjectMapper();
List<MyObject> list = mapper.readValue(json, new TypeReference<List<MyObject>>(){});
System.out.println(list);
}
}
Output:
[MyObject [userID=1, id=1, title=IT, body=123234], MyObject [userID=0, id=2, title=Accounting, body=adsfnsdf]]

Error com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input

I'm trying to convert the next string:
"{ \"contacts\": [{\"name\":\"1\",\"phone\":\"+123456\"}]}"
to some Custom object:
public class CustomObject{
private List<Contact> contacts;
public CustomObject(){
}
public CustomObject(List<Contact> contacts) {
this.contacts = contacts;
}
public List<Contact> getContactList() {
return contacts;
}
public void setContactList(List<Contact> contacts) {
this.contacts = contacts;
}
}
In addition, there is another object within this CustomObject:
public class Contact {
private String name;
private String phone;
public Contact() {
}
public Contact(String name, String phone) {
this.name = name;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
}
Right now, I'm trying to do the following:
private List<Contact> parseString(String jsonAsString) throws IOException {
ObjectMapper mapper = new ObjectMapper();
CustomObject customObject = mapper.readValue(jsonAsString, CustomObject .class);
return customObject .getContactList();
}
But I'm getting the next error:
com.fasterxml.jackson.databind.exc.MismatchedInputException: No content to map due to end-of-input
at [Source: (String)""{ \"contacts\": [{\"name\":\"1\",\"phone\":\"+972545519713\"}]}""; line: 1, column: 1]
Jackson is clever, but not that clever.
Your public setter setContactList cannot be recognized during de-serialization without an annotation.
You have two choices:
Annotate it with #JsonProperty("contacts")
Change it to setContacts
Other choices would include changing your JSON or making the actual fields accessible - not elaborating as they would very likely be bad choices.
Also consider revising your code in other places, e.g. for the getContactList getter if you plan on serializing instances of your POJO.

How to use Jackson ObjectMapper to convert to Pojo for multiple data

I would like to convert the following string/ JSONObject to POJO,
{"list":["\r\n{\r\n\"id\":\"1\",\r\n\"protocol\":\"udp\",\r\n\"srcPorts= \":\"3000-4000 \",\r\n\"destPorts\":\"1790-2000\"\r\n}","\r\n{\r\n\"id\":\"2\",\r\n \"protocol\":\"tcp\",\r\n\"srcPorts\":\"3000-4000\",\r\n\"destPorts\":\"1790-2000 \"\r\n}"],"user":"\r\n{\r\n\"name\":\"John\",\r\n\"address\":\"x.x.x.x\",\r\n\"email \":\"john#p.com\"\r\n}"}
How do I convert to Pojo using Jackson ObjectMapper.
The 2 Pojo classes are as follows.
The user part in the string above should map to the java file - User.java
public class User
{
private String name;
private String address;
private String email;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getAddress()
{
return address;
}
public void setaddress(String Address)
{
this.address = address;
}
public String getEmail()
{
return email;
}
public void setEmail(String email)
{
this.email = email;
}
}
The List part in the string above should map to the java file - TestCase.java
public class TestCase
{
private String id;
private String protocol;
private String srcPorts;
private String destPorts;
public String getProtocol()
{
return protocol;
}
public void setProtocol(String protocol)
{
this.protocol = protocol;
}
public String getSrcPorts()
{
return srcPorts;
}
public void setSrcPorts(String srcPorts)
{
this.srcPorts = srcPorts;
}
public String getDestPorts()
{
return destPorts;
}
public void setDestPorts(String destPorts)
{
this.destPorts = destPorts;
}
public String getID()
{
return id;
}
public void setID(String id)
{
this.id = id;
}
}
Following code should help.
class ParseJson{
private User user;
private TestCase testCase;
//getter and setter methods
}
//and then call objectMapper -
String jsonString = "";//Json input
ObjectMapper mapper = new ObjectMapper();
ParseJson parsedJson = mapper.readValue(jsonString, ParseJson.class);
User user = parsedJson.getUser();
TestCase testCase = parsedJson.getTestCase();
Since your JSON object does not contain any type information, the best approach would be to use a custom deserializer class for Jackson, at least for the outer class. Alternatively, you can try annotating your POJO classes with Jackson annotations, and hope that the Right Thing happens.
In any case, you will have to make Jackson aware of your context by calling one of the ObjectMapper.readValue() methods with the proper class type argument, so that Jackson will know what it is that is being deserialized.

Categories