Get values by key from JSON Multi-dimensional Array - java

JSON:
[{"id":141741,"name":"Group","nodeTypeId":3,"deleted":false,"hasNodeAccesses":false,"children": [{"id":141742,"name":"Division","nodeTypeId":14,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141743,"name":"Site 1","nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141746,"name":"Converting","nodeTypeId":5,"deleted":false,"hasNodeAccesses":false,"children":[]}]},{"id":141744,"name":"Site 2","nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141748,"name":"Converting","nodeTypeId":5,"deleted":false,"hasNodeAccesses":false,"children":[]}]},{"id":141745,"name":"Site 3","nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141750,"name":"Converting","nodeTypeId":5,"deleted":false,"hasNodeAccesses":false,"children":[]}]},{"id":141752,"name":"ML1","nodeTypeId":12,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141755,"nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[]}]},{"id":141753,"name":"ML2","nodeTypeId":12,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141756,"nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[]}]},{"id":141754,"name":"ML3","nodeTypeId":12,"deleted":false,"hasNodeAccesses":false,"children":[{"id":141757,"nodeTypeId":4,"deleted":false,"hasNodeAccesses":false,"children":[]}]}]}]}]
Code:
public List<String> getCapexStrategyNodeNames() {
JsonNode capexStrategyNodeList = client.getCapexStrategyNodes();
JSONArray nodes = capexStrategyNodeList.getArray();
List<JSONObject> nodeList = nodes.toList();
return retrieveValues(nodeList, "name");
}
private List<String> retrieveValues(List<JSONObject> list, String key) {
return list.stream()
.map(val -> val.getString(key))
.collect(Collectors.toList());
}
Output:
[Group]
I'm only retrieving the first value
How do I retrieve all name values from a nested JSON Array?
Thanks in advance!

With your current implementation, you are getting JsonNode object and you are reading it's name property, but you are not reading that property for it's chlildren (inner objects).
You have to query all nested objects recursivly and get a value of field name.
In my opinion the simplest way achieve this is by using JsonPath.
Add this dependency in pom.xml file:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
And here is the code snippet:
public static void main(String[] args) throws JsonProcessingException {
String json = "[{\"id\":141741,\"name\":\"Group\",\"nodeTypeId\":3,\"deleted\":false,\"hasNodeAccesses\":false,\"children\": [{\"id\":141742,\"name\":\"Division\",\"nodeTypeId\":14,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141743,\"name\":\"Site 1\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141746,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141744,\"name\":\"Site 2\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141748,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141745,\"name\":\"Site 3\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141750,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141752,\"name\":\"ML1\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141755,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141753,\"name\":\"ML2\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141756,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141754,\"name\":\"ML3\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141757,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]}]}]}]";
List<Object> names= JsonPath.parse(json)
.read("$..name"); //Recursive descent: Searches for the/specified property name recursively and returns an array of all values with this property name. Always returns a list, even if just one property is found.
System.out.println(names);
}
If you run this code you will see output like this:
["Group","Division","Site 1","Converting","Site 2","Converting","Site 3","Converting","ML1","ML2","ML3"]

Your json has only one object, so it is correct that your output has just one item.
This is the only object present in the array at first level
[{
"id": 141741,
"name": "Group",
"nodeTypeId": 3,
"deleted": false,
"hasNodeAccesses": false,
"children": [...]
}]
So your code should return ["Group"] and it is correct.
Probably you need to navigate through the children and maybe recursively to any nested children. In this case you need to change the algorithm

Library Josson also has the capability. The output is an ArrayNode.
https://github.com/octomix/josson
Josson josson = Josson.fromJsonString("[{\"id\":141741,\"name\":\"Group\",\"nodeTypeId\":3,\"deleted\":false,\"hasNodeAccesses\":false,\"children\": [{\"id\":141742,\"name\":\"Division\",\"nodeTypeId\":14,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141743,\"name\":\"Site 1\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141746,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141744,\"name\":\"Site 2\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141748,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141745,\"name\":\"Site 3\",\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141750,\"name\":\"Converting\",\"nodeTypeId\":5,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141752,\"name\":\"ML1\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141755,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141753,\"name\":\"ML2\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141756,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]},{\"id\":141754,\"name\":\"ML3\",\"nodeTypeId\":12,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[{\"id\":141757,\"nodeTypeId\":4,\"deleted\":false,\"hasNodeAccesses\":false,\"children\":[]}]}]}]}]");
JsonNode node = josson.getNode("cumulateCollect(name, children)");
System.out.println(node.toPrettyString());
Output
[ "Group", "Division", "Site 1", "Converting", "Site 2", "Converting", "Site 3", "Converting", "ML1", "ML2", "ML3" ]

Related

I have to parse JSON response, I have to fetch a child node which is under leet speak (random string which contains symbols, chars, numbers)

I have to parse the JSON response, which has a leet speak, the node which I want to extract is the child of leet speak. am not able to extract the required child form the response.
Ex: following is the JSON structure. from which I want to extract name
"debug": {
"|\"|2()|\\|+3/\\/|)": {
"child1": [],
"child2": {
"Name": "abcd",
"Id": "123"
},
"child3": {
"location": "Delhi"
}
}
}
JSON document is equivalent of the Map, and your leet speak string is a key. That is very bad idea as keys should be easily identifiable. So I would suggest to re-think the structure. May be introduce another level where your leet speak string would be contained as a child under some easily identifiable key. But if you can not do that, you can convert your JSON into Map and then extract ALL keys and then traverse through them and find the one that you need and then get your content by that key
You can create pojo and get that node value
Gson g = new Gson();
Pojo1 p1=g.fromJson(jo.toString(), Pojo1.class);
System.out.println(p1.getDebug().get23().getChild2().getName());

parsing a json which has attributes which might come as string or array of strings

I have a nested json where in the innermost array there are some keys for which the values could either be a string array or an array of array of strings. The json format is not consistent. How do I parse such a json using gson.
I have tried to write a custom de-serializer (see Gson - parsing json with field that is array or string) but that is throwing exception even before I could detect the attribute as string or array and then update the attribute accordingly.
my json is like this
{
"hits" : {
"total" : 100,
"max_score" : 1,
"hits": [
{"_index": "s1",
"_source":{
"activeOrExpired":[
["active"]
]
}
},
{"_index": "s1",
"_source":{
"activeOrExpired":[
"expired"
]
}
}
]
}
}
My java classes are
public class OuterJson {
#SerliazedName("hits")
public Hits hitsOuter;
public static class Hits {
public List<InnerHits> innerHits;
}
}
public InnerHits {
public String _index;
public Source _source;
public static class Source {
public List<List<String>> activeOrExpired;//I declare this field as
//list of list of strings
public Source() {
activeOrExpired = new ArrayList<>();
}
}
}
public class CustomDeserializer implements JsonDeserializer<OuterJson> {
#Override
public OuterJson deserialize(JsonElement elem, Type type, JsonDeserializationContext context) throws JsonParseException {
JsonObject outerObj = elem.getAsJsonObject();
JsonElement innerHits = outerObj.get("hits").getAsJsonObject().get("hits");
//I want to then detect the type of "activeOrExpired" and convert it
//to list of list of strings if it is present just as a string
//I am getting exception in the below line
InnerHits[] innerHitsArray = new Gson().fromJson(innerHits, InnerHits[].class);
//omitting below code for brevity since my code is failing above itself.
}
}
The exception is
java.lang.IllegalStateException: Expected BEGIN_ARRAY but was String at path $[0]._source.activeOrExpired[0]
Here the innermost "hits" array has the "_source" array which has a field "activeOrExpired" this field is coming either as an array of Strings or array of array of strings.
How should I design the custom deserializer to handle such case?
I am new to gson and was following the method mentioned in the above link. My code is described above, could anyone please give me some hint on progressing. Thanks!
You can use DSM stream parsing library for such a complex JSON or XML. By using DSM you don't need to create java stub file to deserialize. you can directly deserialize to your own class.
It uses YAML based mapping file.
Here is the solution to your question. I am not sure about your object structure. I only deserialize some part of it.
Mapping File:
result:
type: object # result is map.
path: /hits
fields:
hits:
path: hits
type: array
fields:
index:
path: _index
source:
path: _source/activeOrExpired
filter: $value!=null
type: array # source is also array.
Use DSM to filter JSON and deserialize.
// you can pass your class to deserialize directly to your class instead of getting map or list as a result.
//DSM dsm=new DSMBuilder(new File("path/to/maping.yaml")).create(YourClass.class);
DSM dsm=new DSMBuilder(new File("path/to/maping.yaml")).create();
Map<String,Object> hits= (Map<String,Object>)dsm.toObject(new File("path/to/data.json");
json representation of hits variable
{
"innerHits" : [ {
"index" : "s1",
"source" : [ "active" ]
}, {
"index" : "s1",
"source" : [ "expired" ]
} ]
}

Print JSON with ordered properties

I have JSON with objects in specific order:
{
"Aaa": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
"Test2"
]
}
},
"Bbb": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
"Test2"
]
}
},
"Ddd": {
"Langs": {
"Val": [
"Test"
],
"Pro": [
]
}
},
}
And I would like to add new object Ccc between Bbb and Ddd. I tried to configure object mapper like this:
ObjectMapper mapper = new ObjectMapper()
.enable(SerializationFeature.INDENT_OUTPUT)
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true)
.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
and then print with this code, but Ccc ends at the end of file.
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter();
prettyPrinter.indentArraysWith(DefaultIndenter.SYSTEM_LINEFEED_INSTANCE);
//Write whole JSON in FILE
String finalJson = mapper.writer(prettyPrinter).writeValueAsString(rootFlores);
finalJson = finalJson.replaceAll("\\[ ]", "[" + System.lineSeparator() + " ]");
finalJson = finalJson.replaceAll("/", "\\\\/");
Files.write(Paths.get("DictionaryFlores_new.json"), Collections.singleton(finalJson));
Is here a way how to print JSON ordered?
Jackson deserialization/serialization does not sort properties
According to this answer, the Jackson SORT_PROPERTIES_ALPHABETICALLY only applies to POJO properties, not Maps. In JSON there is no difference between a Map and an Object, so you need to set the order in the Map first by using a LinkedHashMap or TreeMap
By definition, the keys of an object are unordered. I guess some libraries could offer an option to control the order of the keys when stringifying, but I wouldn't count on it.
When you need a certain order in json, you need to use an array. Of course, then you'd have to move the keys to a property in the child objects, and then the resulting array could only be indexed by number (not by the key). So then you might have to do additional processing to covert the data structure in the JSON to the data structure you really want to process.
Since you seems ready to use regex to update a JSON, I would suggest a "safer" approach. Don't try to create a pattern that would unsure that you don't update a value somewhere.
Iterate you values, on object at the time. Stringify the object and append the String yourself. That way, you are in charge of the object order. Example :
StringBuilder sb = new StringBuilder("{");
List<JsonPOJO> list = new ArrayList<>();
//populate the list
for(JsonPOJO pojo : list){
sb.append(pojo.stringify()).append(",");
}
sb.setLength(sb.length() - 1); //remove the last commma
sb.append("}");
Here, you are only managing the comma between each JSON object, not create the "complex" part about the JSON. And you are in full control of the order of the value in the String representation, it will only depend on the way you populate the List.
Note: sorry for the "draft" code, I don't really have access to my system here so just write this snippet to give you a basic idea on how to "manage" a JSON without having to recreating an API completely.
Note2: I would note suggest this unless this looks really necessary. As you mention in a comment, you are have only the problem with one key where you already have a JSON with 80000 keys, so I guess this is a "bad luck" scenario asking for last resort solution ;)

GSON - How to only return a list of a certain subobject?

I am using an api to get data for my application. I parse the JSON data using GSON so I have something looing like this:
List
obj
double
movie
...
obj
double
movie
...
...
The double value is used for weighting the order of the movies. However, I am not intrested in that, and would instead look a plain list of movies, like this:
List
movie
...
movie
...
...
The only solution I can think of is first parsing it to a list of obj and then using a loop to copy the movie objects to a new list, but I was hoping you could teach me a better method. What are your suggestions?
You can use JsonPath library, along with JSONExpression to extract the required element(s) from JSON. E.g. in case of below json:
[{
"test": 1,
"test1": "value1"
},
{
"test": 1,
"test1": "value2"
}]
If you want to get all the values of test1 element then, you can use $[*].test1 JSONExpression to get both value1 and value2. See the java example below:
public static void main(String[] args) {
Object value = JsonPath.read("[{ \"test\": 1, \"test1\": \"value1\"},{ \"test\": 1, \"test1\": \"value2\"}]", "$[*].test1");
System.out.println(value);
}
This is a good website to test JSON expressions on the fly.
*edit*
Maven dependency for JsonPath :
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.0.0</version>
</dependency>
I seem to recall that giving your field the transient keyword should do the trick:
private transient double value;
This should exclude it from serialization.

change json elements' value in javax.json.JsonArray

[
{
"key":"key1",
"value":"key one value",
"description":""
},
{
"key":"key2",
"value":"key two value",
"description":""
},
{
"key":"key3",
"value":"key three value",
"description":""
},
{
"key":"key4",
"value":"key four value",
"description":""
},
{
"key":"key5",
"value":"key five value",
"description":""
}
]
This above is my an example json file I'm working with, I'm putting it into an JsonArray like this
BufferedReader reader = Files.newBufferedReader(file,
Charset.defaultCharset());
JsonReader jsonReader = Json.createReader(reader);
JsonArray array = jsonReader.readArray();
And My issue is I want to access the JsonArray and change the value part of each json element but is unable to do this.
the collection doesn't seem to offer anyways to replace values of any json element.
Do you know anyways I could achieve what I'm set to to do??
PS: also open to suggestions on using an alternative collection, but please educate me on why should I choose said collection.
Since you did not mention which JSON library you are using, you can use element method if you are using json-lib to replace an element
public JSONArray element(int index,
Object value)
If you want to update a specific attribute of the JSONObject element, you can try something like below
array.getJSONObject(0).put("key","new key value")
Please note that I have used hard coded value 0 for demonstration purposes.

Categories