JSON: Parse ArrayNode of Arrays - java

I'm trying to parse a JSON ArrayNode in Java but I'm having some issues.
The object is as follows:
{
"type": "type",
"id": "id",
"attributes": {
"x": [ "x.value" ],
"y": [ "y.value" ],
"z": [ "z.value" ]
}
}
I'm parsing it as follows:
Map<String, Map<String, String>> users = new HashMap<>();
Iterator<JsonNode> arrayIterator = dataArray.elements();
while (arrayIterator.hasNext())
{
JsonNode r = arrayIterator.next();
String id = r.get("id").asText();
users.put(id, new HashMap<>());
Iterator<JsonNode> attributeIterator = r.path("attributes").elements();
while (attributeIterator.hasNext())
{
JsonNode attribute = attributeIterator.next();
users.get(id).put(attribute.asText(),
attribute.elements().next().asText());
}
}
But I'm getting a map like this:
"" => z.value
I found out in Java' documentation that the attribute .asText() will return empty if it is not a value node. How can I get that name so my map is instead:
x => x.value
y => y.value
z => z.value

Well the first thing you need the keys of your JSON. So I tried with fields instead of only elements
Iterator<Map.Entry<String, JsonNode>> attributeIterator = dataArray.path("attributes").fields();
while (attributeIterator.hasNext())
{
Map.Entry<String, JsonNode> attribute = attributeIterator.next();
users.get(id).put(attribute.getKey(),
attribute.getValue().get(0).asText());
}
I didn't like to get an array So I change to this
Iterator<Map.Entry<String, JsonNode>> attributeIterator = dataArray.path("attributes").fields();
while (attributeIterator.hasNext())
{
Map.Entry<String, JsonNode> attribute = attributeIterator.next();
users.get(id).put(attribute.getKey(),
attribute.getValue().elements().next().textValue());
}
The reason I used fields because I needed the key value :
Iterator that can be used to traverse all key/value pairs for object
nodes; empty iterator (no contents) for other types
And elements doesn't include keys:
Method for accessing all value nodes of this Node, iff this node is a
JSON Array or Object node. In case of Object node, field names
(keys) are not included, only values. For other types of nodes,
returns empty iterator.
From Java Docs
This is getting the map filled. I used jackson 2.9.4

Related

Convert a List of JSON-objects input into a nested Map

I have a String input in the following format:
Input String: [{ "id":"1", "name":"A", "address":"St 1"},{ "id":"2", "name":"B", "address":"St 2"}, ...]
And I want to be able to convert this to a Map<String, Map<String, String>> format.
So, something like:
Required Output Format: {1: {id:1, name:"A", address: "St 1"} 2: {id:2, name:"B", address: "St 2"}}
I created a class to help in parsing the input:
public class Student{
private String id;
private String name;
private String address;
}
Which I am trying to do through Jackson's ObjectMapper to get the data in the format: List<Student> format and then use Collectors.toMap() to convert it to a Map of Maps format.
All the examples I have seen so far suggest an approach like:
List<Student> student = objectMapper.readValue(inputString, new TypeReference<List<Student>>(){});
Map<String, Student> studentTempMap = student.stream()
.collect(Collectors.toMap(
Student::getId,
Function.identity()
));
Which makes the studentTempMap something like:
{ 1: object of Student("id":"1", "name":"A", "address":"St 1"),
2: object of Student("id":"2", "name":"B", "address":"St 2"),
... }
A brute force approach from here:
create a new Map studentMap
Iterate over keys (which are "id") in studentTempMap.
Then create another Map, temp.
Add keys "id", "name", and "address" and values using something like studentTempMap.get(2).get("id"), and something similar for all the other keys (name and address). Where 2 would be the current iterator key over the Map studentTempMap.
Finally add a key say as 2 (current iterator) and value temp in the studentMap.
I do not want to use this brute force approach as I have a large number of Student objects.
Is there a way through ObjectMapper to get the output directly in the form of Map<String, Map<String, String>> format?
Or is there a way through Collectors.toMap to parse it to the above format?
I want to be able to convert this to a Map<String, Map<String, String>>
If you want to obtain a nested map of strings Map<String,Map<String,String>> as a result, you don't need to convert JSON into a list of POJO.
Instead, you can parse JSON into a list of maps List<Map<String,String>> and then generate a nested map.
String inputString = """
[{ "id":"1", "name":"A", "address":"St 1"},
{ "id":"2", "name":"B", "address":"St 2"}]""";
ObjectMapper objectMapper = new ObjectMapper();
List<Map<String, String>> students = objectMapper.readValue(
inputString, new TypeReference<>() {}
);
Map<String, Map<String, String>> studentMapById = students.stream()
.collect(Collectors.toMap(
map -> map.get("id"), // key
Function.identity(), // value
(left, right) -> left // resolving duplicates
));
studentMapById.forEach((k, v) -> System.out.println(k + " : " + v));
Output:
1 : {id=1, name=A, address=St 1}
2 : {id=2, name=B, address=St 2}

Passing JSON keys as String containing a range of numbers

I am trying to have a set of keys to point to a value.
For example the key 0 is to point to the value "EXAMPLE_1"
keys 1,2,3,4,5 is to point to the value "EXAMPLE_2"
and keys 6,7,8,9,10 is to point to the value "EXAMPLE_3"
This is the JSON structure I came up with (which will exist in an external file).
{
"0" : "EXAMPLE_1",
"1,5" : "EXAMPLE_2",
"6,10" : "EXAMPLE_3"
}
Using following code to read and fetch correct value.
private String getValue(String count){
Map<String, String> map = // code to fetch data from the file and get above map. Works.
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
String[] keysInRange = key.split(",");
if(Arrays.asList(keysInRange).contains(count)){
return value;
}
}
}
This technically works but is there a better way to do this.
Looking to improve the JSON structure.
Finding it silly to be passing in the keys in this manner.
Note that the keys would be a single number or always in a range.
You could try below. This is assuming, Keys in range are like this 1,2,3,4,5 for 1,5
private String getValue(String count){
Map<String, String> map = // code to fetch data from the file and get above map. Works.
If(map.containsKey(count)){
return map.get(count);
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if(key.contains(","+count) || key.contains(","+count+",") || key.contains(count+",") ){
return value;
}
}
}
You could change your JSON structure to an array of elements:
{[ {
"name": "EXAMPLE_1",
"from": "0",
"to": "0"
},
{
"name": "EXAMPLE_2",
"from": "1",
"to": "5"
},
{
"name": "EXAMPLE_3",
"from": "6",
"to": "10"
}
]}
and parse them with a JSON parser like Jackson oder GSON in data objects like
class Example {
private String name;
private int from;
private int to;
// ommitted getters & setters for brevity
}
Your method then becomes (using Java 8+ and the streams api):
private String getValue(int count) {
Set<Example> examples = ... // code to fetch data from the file
Optional<Example> match = examples.stream()
.filter(example -> example.getFrom() >= count)
.filter(example -> example.getTo() <= count)
.findFirst();
// or return the Optional<Example> directly
return match.map(Example::getValue).orElse(null);
}

Json string with nested objects to Map<String,String>

I have a json string with possible nested object inside like this:
{
"stringTypeCode": "aaaaa",
"choiceTypeCode1": {
"option1": true,
"option2": true
},
"choiceTypeCode2": {
"option3": true,
"option4": true
}
}
I need it to convert to a Map leaving the nested objects as strings:
stringTypeCode - aaaaa
choiceTypeCode1 - {"option1": true,"option2": true}
choiceTypeCode2 - {"option2": true,"option3": true}
Can it be done in a simple way, preferably without any library?
Edit: or with a library if there is no other simple way.
Edit2: I have a variable number of properties with variable names in the objects.
Parse the json to a map or generic json structure, iterate over the key - value pairs, then create a new map from key - toJsonString(value) pairs. value might be a simple string, json object, number etc...
With a simple Jackson ObjectMapper:
String json = "YOUR JSON HERE";
ObjectMapper mapper = new ObjectMapper();
Iterator<Entry<String, JsonNode>> fields = mapper.readTree(json).fields();
Map<String, String> m = new HashMap<>();
while (fields.hasNext()) {
Entry<String, JsonNode> field = fields.next();
m.put(field.getKey(), mapper.writeValueAsString(field.getValue()));
}
m.entrySet().forEach(e -> System.out.println(e.getKey() + " - " + e.getValue()));
Your example produces:
stringTypeCode - "aaaaa"
choiceTypeCode1 - {"option1":true,"option2":true}
choiceTypeCode2 - {"option3":true,"option4":true}

to retrieve values from hashmap inside a hashmap

i have map,inside another map is there as a value
to retrieve values from hashmap inside a hashmap
map<string,map1<string,object>>
how to get or retrieve values from marks?
{
"name": "mm",
"language known": "English",
"marks": {
"english": [
"88"
]
}
}
You can get inner map like this
Map<String, Object> innermap = outermap.get("marks");
Then use innermap to get value
String value=innermap.get("english");
If you know what key corresponds to the inner map, you should first obtain the inner map:
Map<String, Object> innerMap = outerMap.get("marks");
Then you can get key-value pairs from the inner map using a for-each loop:
for (String key : innerMap.keySet()) {
Object value = innerMap.get(key);
// do something with key and value
}

How to tell Jackson to make a sub-element the top level element

I know how to unwrap an Object in Jackson but I can not find any examples on how to unwrap two layers.
Sharepoint API EndPoints return everything in the following format:
{
"d": {
"results": [
{
/* lots objects with lots of properties
that are irrelevant to the question */
}
]
}
}
I tried the following and it does not make results value the top level object.
this.om.readerFor(new TypeReference<List<User>>() {})
.withRootName("d")
.readValue(response.parseAsString());
How do I tell Jackson to start parsing the the results as a top level Array without having to create a custom object hierarchy and without having to write a custom deserializer. I have a custom object that is annotated that this will populate, I want TypeSafety so Map<String,Object> is not what I want.
Probably not the most efficient way, but can try to parse object as a tree first and then navigate to the node you need. Something like this:
mapper.readerFor(new TypeReference<List<User>>() {}).readValue(mapper.readTree(json).get("d").get("results"))‌​;
Easier to read:
mapper.convertValue(mapper.readTree(json).get("d").get("‌​results"), new TypeReference<List<User>>() {});
#JsonRootName("d")
class Data {
public Map<String, Object>[] results;
}
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.WRAP_ROOT_VALUE);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
Map<String, Object> results = new LinkedHashMap<>();
results.put("key1", "val1");
results.put("key2", "val2");
Data expected = new Data();
expected.results = new Map[1];
expected.results[0] = results;
String json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(expected);
Data actual = mapper.readerFor(Data.class).readValue(json);
Json string looks like:
{
"d" : {
"results" : [ {
"key1" : "val1",
"key2" : "val2"
} ]
}
}
P.S. In case of you do not care about concreate object and Map<String, Object> is OK for you, then you could use like this.
Map<String, Object> results = (Map<String, Object>)((List<?>)mapper.readerFor(Map.class).<Map<String, Map<String, ?>>>readValues(json).next().get("d").get("results")).get(0);

Categories