Is it possible to group JSON fields into one property using Jackson? - java

I'm wondering if there is any way to deserialize several JSON fields to just one Java property. E.g. given this JSON:
{
"id" : "1",
"name" : "Bartolo",
"address" : "whatever",
"phone" : "787312212"
}
deserialize it to this class:
public class Person {
public String id;
public String name:
#JsonProperty(names = {"address", "phone"}) //something like this
public String moreInfo;
}
so moreInfo equals to "whatever, 787312212" or something similar.
Is this possible without using custom deserializer?

You could use the #JsonCreator annotation like following:
String json = {"id" : "1", "name" : "Bartolo", "address" : "whatever", "phone" : "787312212" }
ObjectMapper mapper = new ObjectMapper();
Person person = mapper.readValue(json , Person.class);
and in the constructor of your Person class add this
#JsonCreator
public Person(#JsonProperty("address") String address, #JsonProperty("phone") String phone) {
this.moreInfo = address + "," phone;
}

Another solution, if you don't want to know/handle other fields in the object, but decided to still receive these fields (maybe for logging purposes), then you can put them in a key-value store(Map)
#Getter
private final Map<String, Object> otherFields = new HashMap<>();
#JsonAnySetter
public void set(String name, Object value) {
otherFields.put(name, value);
}
Note that if you have any field with the same name as the Map field(like 'otherFields' in the example above), then you can get MismatchedInputException

Related

Read custom map key using Jackson

Using this input
{
"personId": "uhqwe-8ewn-3129m",
"infoPerson": {
"name": "john",
"age" : 35
}
},
....
And this POJO
public class Person {
private String name;
private int age;
//getter/setter
}
I need to read it as
private Map<String,Person> pMap;
using the personId as the map key.
Is there a way to do it with jackson annotation?
As far as I know, it is not possible.
You can read a list and then convert it to a map.
Something like this
Map<String, Person> map = list.stream()
.collect(Collectors.toMap(Person::getPersonId, person -> person));

Deserialize JSON array in a deserialized JSON array using Jackson

I have a JSON structured like:
{
"id" : "123",
"name" : [ {
"id" : "234",
"stuff" : [ {
"id" : "345",
"name" : "Bob"
}, {
"id" : "456",
"name" : "Sally"
} ]
} ]
}
I want to map to the following data structure:
Class01
#Getter
public class Class01{
private String id;
#JsonDeserialize(using = Class01HashMapDeserialize.class)
private ArrayList<Class02> name;
}
Class02
#Getter
public class Class02{
private String id;
private ArrayList<Class03> stuff;
}
Class03
#Getter
public class Class03{
private String id;
private String name;
}
In my main Method im using an ObjectMapper with objectMapper.readValue(jsonString,new TypeReference<ArrayList<Class02>>(){}) to map this JSON to my Class01. This Class successfully deserealizes the Class02-array into the name array.
When it comes to the second array I don't know how to further deserialize as I am not able to access the json text from the class02 stuff entry.
#Override
public ArrayList<Class02> deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException {
ArrayList<Class02> ret = new ArrayList<Class02>();
ObjectCodec codec = parser.getCodec();
TreeNode classes02 = codec.readTree(parser);
if (classes02.isArray()) {
for (JsonNode class02 : (ArrayNode) classes02) {
if(classe02.get("stuff").isArray()){
ObjectMapper objectMapper = new ObjectMapper();
ArrayList<Class03> classes03 = objectMapper.readValue(class02.get("stuff").asText(), new TypeReference<ArrayList<Class03>>(){});
}
ret.add(new Class02(class02.get("id").asText(), classes03));
}
}
return ret;
}
Why did you put a #JsonDeserialize annotation ? Jackson shall be able to deserialize it just fine without any custom mapping:
#Getter
public class Class01{
private String id;
private ArrayList<Class02> name;
}
Also in a first pass, I would generate the getters/setters/constructor manually for the 3 classes. There may be issues with Lombok & Jackson that you may want to solve later once you made the first version of the code works (Can't make Jackson and Lombok work together)
And your reader shall be more like:
ObjectMapper objectMapper = new ObjectMapper();
String text = ... //Your JSon
Class01 class01 = objectMapper.readValue(text, Class01.class)

JSON Jackson deserialization multiple keys into same field

I am trying to convert JSON into POJO.
I have worked with Jackson to convert standard JSON file.
In this particular case, I would like to overwrite the key value to "default" class/variable. In this case, there are multiple key value to be replaced (ie. hundreds, and the key values to be replaced are unknown).
Is this possible? I thought of storing it into Map, then iterate and store each into POJO, but wondering if there is different option, since I am not that familiar with storing JSON to Map.
Example of the JSON to be processed:
"People" : {
"person1" : {
"name" : "john doe",
"address" : "123 main st",
"email" : "john#doe.com"
},
"person2" : {
"name" : "bob cat",
"address" : "234 dog st",
"email" : "bob#cat.com"
},
"person3" : {
"name" : "foo bar",
"address" : "111 1st ave",
"email" : "foo#bar.com"
},
"person8" : {
"name" : "james bono",
"address" : "999 alaska st",
"email" : "james#bono.com"
}
}
Is it possible to generate the class in the following structure? The main issue is there are hundreds of value to be replaced and assuming they are unknown, I can't use this approach.
#JsonIgnoreProperties(ignoreUnknown = true)
public class People {
#JsonAlias({"person1", "person2"})
private List<Details> person; // --> this should be the default replacing person1, person2, and so on
private class Details {
String name;
String address;
String email;
}
}
You can use JsonAnySetter annotation for all properties personXYZ. See below example:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
System.out.println(mapper.readValue(jsonFile, People.class).getPersons());
}
}
class People {
private List<Details> persons = new ArrayList<>();
#JsonAnySetter
public void setPerson(String name, Details person) {
this.persons.add(person);
}
public List<Details> getPersons() {
return persons;
}
public static class Details {
String name;
String address;
String email;
// getters, setters, toString
}
}
For your JSON above code prints:
[Details{name='john doe', address='123 main st', email='john#doe.com'}, Details{name='bob cat', address='234 dog st', email='bob#cat.com'}, Details{name='foo bar', address='111 1st ave', email='foo#bar.com'}, Details{name='james bono', address='999 alaska st', email='james#bono.com'}]
In case you use inner class remember to make it public static to make it visible to Jackson instantiation process.
See also:
How to use dynamic property names for a Json object
Jackson Annotation Examples
Just adding my two cents. I think this way looks nicer and is also easy to retrieve data. Jackson will do the deserialization without the need of any other tags or custom code, and it's all nice and sorted in a hashmap.
class People {
private Map<String, Person> persons = new HashMap<>();
// getter and setters
}
public class Person {
String name;
String address;
String email;
// getters and setters
}
}

Mapping Json into nested POJO

I've the following JSON from some upstream api
{
"Id": "",
"Name": "",
"Age": ""
}
And I need to map this above json to a downstream request paylaod (POJO) .
public class Employee
{
#JsonProperty("Id")
private Integer Id;
private User user;
}
public class User {
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Right now I'm doing something like
Employee employee = new ObjectMapper().treeToValue(JsonNode node,Employee.class);
But this is giving null in User Object.
The challenge here is , that the json we are getting from upstream can't be changed . So , is there is any way to map the fields into the nested User object , without changing the structure of json received from upstream.
One Solution is : map the fields separately into User object and then set it into the Employee object . But that's not an efficient solution , because for null validations we would need to do validations separately for User and Employee objects. If the nesting is complex then , validation will be hell of replicated code .
Your JSON does not comply with your Employee class.
Because name and age is at the same level as id, but you want to wrapped in a class User.
So either:
Change the json the structure to
{
"id": "",
"user": {
"name": "",
"age": ""
}
}
Or
Unwrap the User class, the Employee class will be:
public class Employee
{
#JsonProperty("Id")
private Integer Id;
#JsonProperty("Name")
private String name;
#JsonProperty("Age")
private String age;
}
Edit
If you can't choose either option 1 or 2, you have only one option left is to create custom deserializer:
Write a deserializer:
public class EmployeeDeserializer extends StdDeserializer<Item> {
public EmployeeDeserializer() {
this(null);
}
public EmployeeDeserializer(Class<?> vc) {
super(vc);
}
#Override
public Employee deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
int id = (Integer) ((IntNode) node.get("Id")).numberValue();
String name = node.get("Name").asText();
String age = node.get("Age")).asText();
User user = new User(name, age);
return new Employee(id, user);
}
}
Then register this deserializer:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Employee.class, new EmployeeDeserializer());
mapper.registerModule(module);
Employee readValue = mapper.readValue(json, Employee.class);
Another way to register deserializer is:
#JsonDeserialize(using = EmployeeDeserializer.class)
public class Employee {
It seems you are not nesting your JSON correctly. Or your Object Structure is wrong.
JSON should be:
{
"Id": "",
"user" : {
"Name": "",
"Age": ""
}
}
The json structure does not match the structure of your classes.
if the json was like;
{
"Id": "an-id,
"user": {
"Name": "Joe",
"Age": "21"
}
}
Then your code to deserialise to an Employee object would work.

How to store values using Jackson JSON to Java

I am having a really hard time understanding how to place a mapped valued into an array and how to iterate that array.
test.json
[
{
"Name": "Bob",
"Nationality": "",
"City": "Chicago",
"Phone" : "451232424234"
},
......continues with more objects
]
testTemplate.java
//imports
#JSONInclude(Json.Include.Non_NULL)
#JsonPropertyOrder({"Name,"Nationality","City","Phone"})
Public class testTemplate {
#JsonProperty("Name")
private String userName;
#JsonProperty("Nationality")
private String nation;
#JsonProperty("City")
private String city;
#JsonProperty("Phone")
private String phone;
#JsonProperty("Name")
public String getName (String userName) {
this.userName = userName;
}
#JsonProperty("Nationality")
public String nation (String nation) {
this.nation = nation;
}
#JsonProperty("City")
public String city (String city) {
this.city = city;
}
#JsonProperty("Phone")
public String phone (String phone) {
this.phone = phone;
}
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
testParse.java
Public Class testParse {
List<testParse> test;
ObjectMapper mapper;
protected void setUp() throws IOException {
mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT();
test = mapper.readValue(this.getClass().getResourcesAsStream("test.json"),
mapper.getTypeFactory().constructCollectionType(List.class, testParse.class));
I need to help first clarifying exactly what the code is doing, and how to put JSON properties (Name, Nationality,City,Phone) into Java.
My understanding is the testTemplate file create the strings in which the properties will be held, then the testParse file has the mapper feature (from Jackson) that reads through the json and puts all into "test" (as an array?)
My goal is in testParse, where if everything thing is in "test", then I read through that, and start to pull it out and place it into a folderList.
public static Map<String, String> userName throws IOException {
Map<String, String> folderList = new TreeMap<>();
//Don't know how, but iterate through "test"
LineIterator it = new LineIterator(//the method used to read the json file);
try {
while(it.hasNext()) {
String line = it.nextLine();
folderList.put(userName,nation) //can I also put city and phone at once as well or should I create another put: folderList.put(userName, city)
return folderList;
How does one do this? Is there a better way to put the properties of json into the folderList after using the jackson mapper?
Actually, testTemplate don't generate anything, what Jackson have done here is just get data from "test.json" as String, then using Reflection read the format of testTemplate.java. Based on template it've just have + setting you add to ObjectMapper object, Jackson will convert test.json to array of object Java.
P/S: you don't need to add annotation in both attributes and get function of POJO class, just do it in get function or attributes only, it's enough for Jackson.

Categories