How to deserialize JSON with #JsonCreator and #JsonGetter - java

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;
}
}

Related

Adding test in JUnit that fails if Serialized Object changes

I have the following class that is created with the usage of a builder:
public class Foo {
private String name;
private int age;
public String getName() { return name; }
public int getAge() { return age; }
private Foo(Builder b) {
name = b.name;
age = b.age;
}
public static final class Builder {
private String name;
private int age;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder age(int age) {
this.age = age;
return this;
}
}
}
Now I want to add a JUnit test for this where if this class were to change (via new field were to be added, some other changes made to this) that would also be reflected once the class got serialized, I want that test to fail to catch that change. I am not aware of any libraries that can do this, how can this be done?

Java Json Jackson saving private fields without getters and setters

I am using Jackson to save my java object (Person.class) as a json file and load from it using jackson as well.
This is what I am saving at the moment:
public class Person {
private String name;
private int yearOfBirth;
public Person(String name, int yearOfBirth) {
this.name = name;
this.yearOfBirth = yearOfBirth;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getYearOfBirth() {
return yearOfBirth
}
public void setYearOfBirth(int yearOfBirth) {
this.yearOfBirth = yearOfBirth;
}
}
Even though a person's name (in this case) CANNOT be changed, nor can their year of birth, I have to have the getters and setters for Jackson to recognise the values otherwise it will give an exception:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "name"
How can i make my fields name and yearOfBirth (without making them PUBLIC ofcourse) final fields uneditable after initialisation.
This is my saving and loading using jackson:
saving:
public void savePerson(File f, Person cache) {
ObjectMapper saveMapper = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
saveMapper.setVisibilityChecker(
saveMapper.getSerializationConfig().
getDefaultVisibilityChecker().
withFieldVisibility(JsonAutoDetect.Visibility.ANY).
withGetterVisibility(JsonAutoDetect.Visibility.NONE).
withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)
);
ObjectWriter writer = saveMapper.writer().withDefaultPrettyPrinter();
writer.writeValue(f, cache);
}
loading:
public Person load(File f) {
return new ObjectMapper().readValue(f, Person.class);
}
User #JsonProperty and it will work.
import com.fasterxml.jackson.annotation.JsonProperty;
public class Person {
private final String name;
private final int yearOfBirth;
public Person(#JsonProperty("name") String name, #JsonProperty("yearOfBirth") int yearOfBirth) {
this.name = name;
this.yearOfBirth = yearOfBirth;
}
public String getName() {
return name;
}
public int getYearOfBirth() {
return yearOfBirth;
}
}

Spring boot, Jackson Json Issue while serializing and deserializing

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/

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.

Jackson JSON processor problems

I have been scratching my head over this for hours now :
Jsckson deserializes B but bombs on C below :
B and C are both subclasses of A, and thus has a setter getName.
Note that the uppercase N in Name is intentional, that is how my JSON looks.
Deserializing C complains about Unrecognized field name Name, for B it is OK.
Version 1.7.2
ObjectMapper mapper = new ObjectMapper();
mapper.getDeserializationConfig().addMixInAnnotations(B.class, MixIn.class);
String json = "{\"Name\" : \"13\"}";
B b = m.readValue(json, B.class);
System.out.println(b.getName());
C c = m.readValue(json, C.class);
System.out.println(c.getName());
public class A {
private int id ;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class B extends A {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
public class C extends A {
private String country;
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
}
#JsonAutoDetect(fieldVisibility = Visibility.NONE,setterVisibility=Visibility.ANY)
abstract class MixIn {
#JsonProperty("Name")
public abstract void setName(String name);
#JsonProperty("Id")
public abstract void setId(int id);
}
This is because the default JSON field name to match a setter called setName is name, not Name. This is the Java property name convention.
The deserialization works for B because you've introduced the mixin that alters the field name that matches against setName.
Can't you just introduce the mixin for C also?
You only added the name-uppercasing annotations to B.
mapper.getDeserializationConfig().addMixInAnnotations(B.class, MixIn.class);
To get C to work you need to do it for C as well.
mapper.getDeserializationConfig().addMixInAnnotations(C.class, MixIn.class);

Categories