Access json nested element directly in java - java

I have json string i want to direclty access element like childMap4.childMap3.ch3Name
and my json string is i am not able find proper solutions.
{
"is working": true,
"childMap4": {
"childMap3": {
"childMap2": {
"childMap1": {
"ch1Name": "childMap1"
},
"ch2Name": "childMap2"
},
"ch3Name": "childMap3 testing"
},
"ch4Name": "childMap4"
},
"prName": "parentMap",
"salary": 800.23,
"prName2": "parentMap field 2",
"age": 24
}

If you just want to extract the value, you should take a look at JsonPath.
Just use the method JsonPath.read([json_object], [expression]) with expression $.childMap4.childMap3.ch3Name.
Another approach would be to create proper mapping class and deserialize JSON string with a library like Jackson, but if you want just single field value taht would be an overkill.

JsonNode jsonNode = mapper.convertValue(Obj, JsonNode.class);
jsonNode.path("childMap4").path("childMap3").path("ch3Name").toString();

Related

How to add ObjectNodes to ArrayNodes without overwriting old data

I was trying to call data from Directus API, and return certain data in JSON Format on my localhost.
I'm working on a project where we need to build an API layer.
My code:
public String findAllPersons(HttpServletResponse response, String showFields) throws IOException{
try {
// Call to the database (this part is normally not a problem
String url = "https://cuxhsjf3.directus.app/items/blog";
PersonRoot personRoot = template.getForObject(url, PersonRoot.class);
// I used the Objectmapper, since I'm going from normal data to Json.
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode personsObject = objectMapper.createObjectNode();
ArrayNode persons = objectMapper.createArrayNode();
// The array is going to loop through all the data objects from the database
for (int i = 0; i < personRoot.data.toArray().length; i++) {
// I put person objects into an array, and I return this array.
personsObject.put("id", personRoot.data.get(i).id);
personsObject.put("firstName", personRoot.data.get(i).firstName);
persons.add(personsObject);
System.out.println(persons);
}
String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(persons);
return json;
}
// The 'catch' was omitted as not related to the issue
Expected result:
{
"id": "050469ed-0501-4506-9951-794b41bf7e7f",
"firstName": "Elias"
},
{
"id": "0bfb52f7-3656-4202-8c24-2b63eaeca6a9",
"firstName": "Mathias"
},
{
"id": "3145fb95-afd7-4bc4-a62e-a8622b301db2",
"firstName": "Brent"
},
{
"id": "5b93c9b1-4bd1-4aa5-a5ca-d46e849cc58f",
"firstName": "Jef "
},
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
}
}
The output I'm getting:
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
},
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
},
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
},
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
},
{
"id": "cb3d5d02-6b87-4aa1-b817-17550b3cf03c",
"firstName": "Jan"
}
}
As #markspace has pointed out in the comments, you're repeatedly adding the reference to the same ObjectNode into the same JsonArray. When you're modifying this single object, all the elements in the get updated, and what you're getting is data of the very last person replicated multiple time.
You need to move creation of the ObjectNode into the loop, so that a new node would be instantiated for each person:
for (int i = 0; i < personRoot.data.toArray().length; i++) {
ObjectNode personsObject = objectMapper.createObjectNode(); // move object-creation inside the loop
personsObject.put(...);
personsObject.put(...);
persons.add(personsObject);
}
Sidenotes
Encapsulation in your custom class PersonRoot is broken. In Java, we use access modifier like protected and private to hide and protect the data within a class instance. And class is supposed to provide behavior which allow interacting with the state of its instances instead of exposing the instance variables directly (getters are the simplest example of such behavior). This might look alien to you, if previously you were working with languages like JavaScript, but Encapsulation is one of the crucial parts of Object-oriented programming, and it's a common practice in Java to design classes in such a way so that their data is properly encapsulated.
You might also want to learn about one of the GRASP design-patterns, namely The information expert pattern. Which states that an object containing the data should be provided with behavior that facilitates interaction with the data (it's not the responsibility of the code that make use of this object).
I.e. instead of doing this: personRoot.data.toArray().length you can introduce length() method in the PersonRoot class. And instead of writing personRoot.data.get(i) there could be a method like PersonRoot.getPerson(int index).
You can also make PersonRoot implement Iterable interface, so that it could be used as a source of data within an enhanced for-loop.

Check whether json attribute is a list or an object in java

I want to convert JSON returned from a service into a java object class. However, I get two types in the same JSON attribute and with that I get the following error Expected begin array but it was begin object. How to test before parsing JSON for my java class? Example of a JSON below:
{
"name": "ROMEU",
"age": "24",
"phone": "xx xxxx xxxx",
"family": [
{
"kinship": "brother",
"age": "20"
},
{
"kinship": "sister",
"age": "25"
}
]
}
{
"name": "ROMEU",
"age": "24",
"phone": "xx xxxx xxxx",
"family": {
"kinship": "mother",
"age": "20"
}
}
Assuming you are using Jackson, you can use ACCEPT_SINGLE_VALUE_AS_ARRAY
final ObjectMapper mapper = new ObjectMapper()
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
If using GSON, something like
JSONObject response=new JSONObject(res);
if(res.get("family") instanceOf JSONObject) {
// code for JSONObject
} else if(res.get("family") instanceOf JSONArray) {
// code for JSONOArray
}
The correct way to fix this is to change the service that returns such a JSON. It makes no sense to either return a List or a single object for the attribute family. This is complexifying what should be simple.
Why not returning always a List, that may happen to include a single object?
If this is not possible, then you will need all sorts of Jackson deserialization features to make this work. And why? Simply because the service that you are consuming is badly designed.

How to deserialize an array with leading label in front without using wrapper object?

My web service (WS) receives an HTTP POST request and JacksonJsonProvider is deserializing incoming body object into JSON string. The DTO is simple:
public class SettingDTO {
private String key;
private String value;
...
}
The WS signature looks like this:
#Post
Response saveList(List<SettingDTO> list);
The WS is awaiting an array in the input. Example:
{
"settings": [
{
"key": "key1",
"value": "val1"
},
{
"key": "key2",
"value": "val2"
}
]
}
This results in an exception. Jackson does not know how to handle the leading "settings" label. If I try it without the label, just a plain array, it works well. But the requirement is set to use it the way it is.
One solution I know is to use a wrapper object, another DTO. I wonder if this could be solved without an extra wrapper? Maybe an annotation will do the job?
After deserialization, I want to end up with the populated List<Setting> settings ...
There are several ways can achieve this. But if you don't want to use an extra wrapper class, one way is to read the inner JSON string first, then deserialize it to List<SettingDTO> as follows:
Code snippet
ObjectMapper mapper = new ObjectMapper();
JsonNode root = mapper.readTree(jsonStr);
String settingsStr = root.get("settings").toString();
List<SettingDTO> settings = mapper.readValue(settingsStr, new TypeReference<List<SettingDTO>>(){});
System.out.println(settings.toString());
Console output
[SettingDTO [key=key1, value=val1], SettingDTO [key=key2, value=val2]]
BTW, if you have tried to add #JsonRootName(value = "settings") to class SettingDTO, AFAIK, it doesn't work for JSON array!

Pass object inside existing model object, where the new object parameters are not defined and may change

Problem: I have a request body where I have a predefined POJO class, inside this class I need to add another object as parameter. This new object at a given time may have random properties/attributes/params. How can I achieve this?
{
"id": "{{id}}",
"enableTouchId": true,
"idleLogoutMinutes": 10,
"platformSpecificPreferences": {
"ios": {
"terms": "1234",
"privacy": "12345"
},
"web": {
"terms" : "abc"
},
"android": {
"newProperty" : "newValue"
}
}
}
So the new object I am trying to add is platformSpecificPreferences, which when hit using rest calls might or might not have all the properties shown here, which is why I cannot use redefined POJO class for platformSpecificPreferences and create its object.
Solution I tried:
I thought of using JsonObject inside request body, which makes
#JsonProperty("platformSpecificPreferences")
private JsonObject platformSpecificPreferences;
but the problem is, I am not able to hit the api as it doesnt accept this parameter and gives 404.
Thanks in advance.
You can use, kind must a predefined pojo for platformSpecificPreferences but in the pojo you need to ignore values that are not given in the rest call!
You can do this with a json annotation:#JsonIgnoreProperties(ignoreUnknown = true) in the Pojo above the class.

Is it possible to change order of attributes when toJson()?

Let's take this example. I have a pojo class as below.
public class MyRecord{
private String name;
private String id;
//constructors and getters,setters
}
when I get the toJson(new MyRecord("MyName","myId") output for above I can get.
{
"name": "MyName",
"id": "123"
}
And I have inherited one as follows to add the dateTime.
public class MyRecordWithDateTime extends MyRecord{
private String DateTime;
//constructors and getters,setters
}
so when I called toJson(new MyRecordWithDateTime("2016-01-01", "MyName", "myId"))
The output is this
{
"name": "MyName",
"id": "123",
"dateTime": "2016-01-01"
}
but I actually need that as follows. (dateTime should come first.)
{
"dateTime": "2016-01-01",
"name": "MyName",
"id": "123"
}
Is there anyway to do that with keeping inheritance?
Maybe kinda late but just in case there is this annotation #JsonPropertyOrder
Field/member/attributes in a JSON collection do not have an order, and as far as this JSON data structure is concerned, the "order" doesn't matter.
The only reason I can imagine you are concerned with the order is for printing/presentation purposes. In that case I suggest you manually construct the JSON string yourself.
Before you put it in your JSON file, you can try to make an ordered List ( LinkedList, ArrayList or something like), then sort it as you want and after that put it in JSON.
But obviously, a better idea than mine exists!!
But the fact is: JSON doesn't need to be sorted! You just use the getter and it will find your value associated to the key! Even if it's in the last position.

Categories