Jackson converting dynamic json to map - java

I have a problem where some structure of the json is fixed while some part is dynamic. The end output has to be an object of type
Map<String,List<Map<String,String>>>
I am pasting a sample json code for which the jackson work -
{
"contentlets": [
{
"template": "8f8fab8e-0955-49e1-a2ed-ff45e3296aa8",
"modDate": "2017-01-06 13:13:20.0",
"cachettl": "0",
"title": "New Early Warnings",
"subscribeToListIi": "am#zz.com",
"inode": "15bd497-1d8e-4bc7-b0f4-c799ed89fdc9",
"privacySetting": "public",
"__DOTNAME__": "New gTLD Early Warnings",
"activityStatus": "Completed",
"host": "10b6f94a-7671-4e08-9f4b-27bca80702e7",
"languageId": 1,
"createNotification": false,
"folder": "951ff45c-e844-40d4-904f-92b0d2cd0c3c",
"sortOrder": 0,
"modUser": "dotcms.org.2897"
}
]
}
ObjectMapper mapper = new ObjectMapper();
Map<String,List<Map<String,String>>> myMap=mapper.readValue(responseStr.getBytes(), new TypeReference<HashMap<String,List<Map<String,String>>>>() {});
The above code is working fine but when the json changes to (basically a metadata tag is added) it is not able to convert to map.
{
"contentlets": [
{
"template": "8f8fab8e-0955-49e1-a2ed-ff45e3296aa8",
"modDate": "2017-01-06 13:13:20.0",
"cachettl": "0",
"title": "New gTLD Early Warnings",
"subscribeToListIi": "aml#bb.com",
"inode": "15bd4057-1d8e-4bc7-b0f4-c799ed89fdc9",
"metadata": {
"author": "jack",
"location": "LA"
},
"privacySetting": "public",
"__DOTNAME__": "New gTLD Early Warnings",
"activityStatus": "Completed",
"host": "10b6f94a-7671-4e08-9f4b-27bca80702e7",
"languageId": 1,
"createNotification": false,
"folder": "951ff45c-e844-40d4-904f-92b0d2cd0c3c",
"sortOrder": 0,
"modUser": "dotcms.org.2897"
}
]
}

This is expected since the type of the value of metadata is not a String. If you change the type of the map accordingly then it works:
Map<String,List<Map<String,Object>>> myMap = mapper.readValue(reader, new TypeReference<HashMap<String,List<Map<String,Object>>>>() {});
Of course you are left with the problem that values in the map are not of the same type. so you need to ask yourself what is the desired data structure you want and how you further process it. However, one cannot deserialize a json structure into a simple String.

Related

Get json object in Array based on key and value in Java

I have a Json body like the example below. I need to extract the value from a key that has another key with a specific value in an array. I am passing in a JsonNode with everything in the detail component of the message, I can easily extract from each level, however, I'm struggling with the array.
In this case, I need to extract the value of "value" (Police/Fire/Accident Report) from the object in the array which has a key/value pair of "name":"documentTitle". I understand this is a JSONArray, but I can't find a good example that shows me how to extract the values for an object in the array that contains a certain key/value pair, I don't think I can rely on getting the object in position [2] in the array as the same objects may not always be present in the additionalMetadata array.
Sample Json:
"sourceVersion": "1.0",
"eventId": "8d74b892-810a-47c3-882b-6e641fd509eb",
"clientRequestId": "b84f3a7b-03cc-4848-a1e8-3519106c6fcb",
"detail": {
"stack": "corona",
"visibilityIndicator": null,
"documentUid": "b84f3a7b-03cc-4848-a1e8-3519106c6fcb",
"additionalMetadata": [
{
"name": "lastModifiedDate",
"value": "2021-05-21T04:53:53Z"
},
{
"name": "documentName",
"value": "Police/Fire Report, 23850413, 2021-05-20 14:51:23"
},
{
"name": "documentTitle",
"value": "Police/Fire/Accident Report"
},
{
"name": "documentAuthor",
"value": "System Generated"
},
{
"name": "lastModifiedBy",
"value": "System Updated"
},
{
"name": "createdBy",
"value": "System Generated"
},
{
"name": "documentDescription",
"value": "Police/Fire Report received"
},
{
"name": "organizationCode",
"value": "Claims"
}
]
}
}```
Loop through the json array and extract the json object with name documentTitile. From that json object you can get the value
Well, either the JSON framework you're using supports this out of the box (check the documentation) or you could convert it manually to a map:
List<AdditionalMetadataEntry> additionalMetadata;
[...]
Map<String, String> additionalMetadataMap = additionalMetadata.stream().collect(Collectors.toMap(AdditionalMetadataEntry::getName, AdditionalMetadataEntry::getValue));
I was able to figure it out. I created a new node off the existing notificationBody JsonNode, then parsed through the metadata key/value pairs:
String docTitle = "";
JsonNode additionalMetadata = notificationBody.get("detail").get("additionalMetadata");
for (JsonNode node: additionalMetadata) {
String name = node.get("name").asText();
String value = node.get("value").asText();
if(name.equals("documentTitle")){
docTitle = value;
}
}

How to select fields in different levels of a jsonfile with jsonPath?

I want to convert jsonobjcts into csv files. Wy (working) attempt so far is to load the json file as a JSONObject (from the googlecode.josn-simple library), then converting them with jsonPath into a string array which is then used to build the csv rows. However I am facing a problem with jsonPath. From the given example json...
{
"issues": [
{
"key": "abc",
"fields": {
"issuetype": {
"name": "Bug",
"id": "1",
"subtask": false
},
"priority": {
"name": "Major",
"id": "3"
},
"created": "2020-5-11",
"status": {
"name": "OPEN"
}
}
},
{
"key": "def",
"fields": {
"issuetype": {
"name": "Info",
"id": "5",
"subtask": false
},
"priority": {
"name": "Minor",
"id": "2"
},
"created": "2020-5-8",
"status": {
"name": "DONE"
}
}
}
]}
I want to select the following:
[
"abc",
"Bug",
"Major",
"2020-5-11",
"OPEN",
"def",
"Info",
"Minor",
"2020-5-8",
"DONE"
]
The csv should look like that:
abc,Bug,Major,2020-5-11,OPEN
def,Info,Minor,2020-5-8,DONE
I tried $.issues.[*].[key,fields] and I get
"abc",
{
"issuetype": {
"name": "Bug",
"id": "1",
"subtask": false
},
"priority": {
"name": "Major",
"id": "3"
},
"created": "2020-5-11",
"status": {
"name": "OPEN"
}
},
"def",
{
"issuetype": {
"name": "Info",
"id": "5",
"subtask": false
},
"priority": {
"name": "Minor",
"id": "2"
},
"created": "2020-5-8",
"status": {
"name": "DONE"
}
}
]
But when I want to select e.g. only "created" $.issues.[*].[key,fields.[created]
[
"2020-5-11",
"2020-5-8"
]
This is the result.
But I just do not get how to select "key" and e.g. "name" in the field issuetype.
How do I do that with jsonPath or is there a better way to filter a jsonfile and then convert it into a csv?
I recommend what I believe is a better way - which is to create a set of Java classes which represent the structure of your JSON data. When you read the JSON into these classes, you can manipulate the data using standard Java.
I also recommend a different JSON parser - in this case Jackson, but there are others. Why? Mainly, familiarity - see later on for more notes on that.
Starting with the end result: Assuming I have a class called Container which contains all the issues listed in the JSON file, I can then populate it with the following:
//import com.fasterxml.jackson.databind.ObjectMapper;
String jsonString = "{...}" // your JSON data as a string, for this demo.
ObjectMapper objectMapper = new ObjectMapper();
Container container = objectMapper.readValue(jsonString, Container.class);
Now I can print out all the issues in the CSV format you want as follows:
container.getIssues().forEach((issue) -> {
printCsvRow(issue);
});
Here, the printCsvRow() method looks like this:
private void printCsvRow(Issue issue) {
String key = issue.getKey();
Fields fields = issue.getFields();
String type = fields.getIssuetype().getName();
String priority = fields.getPriority().getName();
String created = fields.getCreated();
String status = fields.getStatus().getName();
System.out.println(String.join(",", key, type, priority, created, status));
}
In reality, I would use a CSV library to ensure records are formatted correctly - the above is just for illustration, to show how the JSON data can be accessed.
The following is printed:
abc,Bug,Major,2020-5-11,OPEN
def,Info,Minor,2020-5-8,DONE
And to filter only OPEN records, I can do something like this:
container.getIssues()
.stream()
.filter(issue -> issue.getFields().getStatus().getName().equals("OPEN"))
.forEach((issue) -> {
printCsvRow(issue);
});
The following is printed:
abc,Bug,Major,2020-5-11,OPEN
To enable Jackson, I use Maven with the following dependency:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
In case you don't use Maven, this gives me 3 JARs: jackson-databind, jackson-annotations, and jackson-core.
To create the nested Java classes I need (to mirror the structure of the JSON), I use a tool which generates them for me using your sample JSON.
In my case, I used this tool, but there are others.
I chose "Container" as the name of the root Java class; a source type of JSON; and selected Jackson 2.x annotations. I also requested getters and setters.
I added the generated classes (Fields, Issue, Issuetype, Priority, Status, and Container) to my project.
WARNING: The completeness of these Java classes is only as good as the sample JSON. But you can, of course, enhance these classes to more accurately reflect the actual JSON you need to handle.
The Jackson ObjectMapper takes care of loading the JSON into the class structure.
I chose to use Jackson instead of JsonPath, simply because of familiarity. JsonPath appears to have very similar object mapping capabilities - but I have never used those features of JsonPath.
Final note: You can use xpath style predicates in JsonPath to access individual data items and groups of items - as you describe in your question. But (in my experience) it is almost always worth the extra effort to create Java classes, if you want to process all your data in more flexible ways - especially if that involves transforming the JSON input into different output structures.

Parsing JSON response when returned as Array

com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
This is the error I continue to get while attempting to parse my incoming JSON response data. I'm utilizing the OkHttp library to create and call, and the API I'm getting results from returns everything in an Array as follows:
[
{
"id": 4256,
"image_url": "https://cdn.pandascore.co/images/league/image/4256/OMEN_Challenger_Series_2019.png",
"live_supported": false,
"modified_at": "2019-10-30T10:02:42Z",
"name": "OMEN Challenger",
"series": [
{
"begin_at": "2019-11-01T03:30:00Z",
"description": null,
"end_at": null,
"full_name": "2019",
"id": 1932,
"league_id": 4256,
"modified_at": "2019-10-30T09:11:40Z",
"name": null,
"prizepool": "50000 United States Dollar",
"season": null,
"slug": "cs-go-omen-challenger-2019",
"winner_id": null,
"winner_type": null,
"year": 2019
}
],
"slug": "cs-go-omen-challenger",
"url": "https://omengaming.co/omen_cs/",
"videogame": {
"current_version": null,
"id": 3,
"name": "CS:GO",
"slug": "cs-go"
}
},
{...},
{...},
{...},
{...},
]
I found a lot of folks recommending Gson to parse it into a custom class, but the following code, in theory, should work and it isn't. The parsing doesn't even begin due to it expecting BEGIN_OBJECT and it being BEGIN_ARRAY:
String jsonData = response.body().string();
Gson gson = new Gson();
EventInfo test = gson.fromJson(jsonData, EventInfo.class);
class EventInfo {
String imageURL;
String name;
JSONArray series;
}
You are trying to parse it into an object. But in your response, you can clearly see that it's a list. The parent POJO should have been a list. And inside that list, you should have created another POJO.
In your response parent is found as array but you need to add first parent as JSON object and child as a array or object.
You need response like this
{
"YourArrayName":[
"YourChildObjName":{
"id": 4256,
"image_url": "https://cdn.pandascore.co/images/league/image/4256/OMEN_Challenger_Series_2019.png",
"live_supported": false,
"modified_at": "2019-10-30T10:02:42Z",
"name": "OMEN Challenger",
"series": [
{
"begin_at": "2019-11-01T03:30:00Z",
"description": null,
"end_at": null,
"full_name": "2019",
"id": 1932,
"league_id": 4256,
"modified_at": "2019-10-30T09:11:40Z",
"name": null,
"prizepool": "50000 United States Dollar",
"season": null,
"slug": "cs-go-omen-challenger-2019",
"winner_id": null,
"winner_type": null,
"year": 2019
}
],
"slug": "cs-go-omen-challenger",
"url": "https://omengaming.co/omen_cs/",
"videogame": {
"current_version": null,
"id": 3,
"name": "CS:GO",
"slug": "cs-go"
}
},
{...},
{...},
{...},
{...},
]
}
I hope this can help You!
Thank You
So, I figured it out. Originally I was receiving the same error at a later point; namely when it got to the series key value in the first JSONObject. The original error occurred because I was trying to parse series as a JSONArray, rather than a List<JSONObject> The corrections are below:
String jsonData = response.body().string();
Gson gson = new Gson();
Type listType = new TypeToken<List<EventInfo>>() {}.getType();
List<EventInfo> test = gson.fromJson(jsonData, listType);
And the EventInfo class:
class EventInfo {
String imageURL;
String name;
List<JSONObject> series;
}
Thank you for the advice everyone!

Jackson mapping same JSON nodes with different names as key

I'm working with a RESTful webservice in android, and I'm using Spring for Android with Jackson for the first time.
I'm using this generator to generate the java classes, but I'm in trouble sometimes when an array of the same objects inside JSON have a different names:
"a2e4ea4a-0a29-4385-b510-2ca6df65db1c": {
"url": "//url1.jpg",
"ext": "jpg",
"name": "adobe xm0 ",
"children": {},
"tree_key": []
},
"d3ff3921-e084-4812-bc49-6a7431b6ce52": {
"url": "https://www.youtube.com/watch?v=myvideo",
"ext": "video",
"name": "youtube example",
"children": {},
"tree_key": []
},
"151b5d60-8f41-4f38-8b67-fe875c3f0381": {
"url": "https://vimeo.com/channels/staffpicks/something",
"ext": "video",
"name": "vimeo example",
"children": {},
"tree_key": []
}
All the 3 nodes are of the same kind and can be mapped with the same object, but the generator creates 3 classes for each node with different name.
Thanks for the help.
With Jackson, you can use Map map = new ObjectMapper().readValue(<insert object here>, Map.class);
as mentioned by Programmer Bruce : here

Parsing JSON using Java/Jackson

I have a small test application I'm writing in Java to parse some JSON from the Reddit API. Some sample JSON I'm looking to parse would be like this:
[
{
"kind": "Listing",
"data": {
"modhash": "1jq62oyvwe15aaba7eb18b0b4363b567a00750766351e03dcc",
"children": [
{
"kind": "t3",
"data": {
"domain": "businessinsider.com",
"media_embed": {},
"levenshtein": null,
"subreddit": "Android",
"selftext_html": null,
"selftext": "",
"likes": null,
"saved": false,
"id": "n17u2",
"clicked": false,
"title": "CONFESSION OF A NON-APPLE-FANBOY: Even If The Samsung Galaxy Nexus Is Better, I'm Still Buying An iPhone",
"media": null,
"score": 0,
"over_18": false,
"hidden": false,
"thumbnail": "http://e.thumbs.redditmedia.com/sL0dCwGAvWqnY_sd.jpg",
"subreddit_id": "t5_2qlqh",
"author_flair_css_class": null,
"downs": 2,
"is_self": false,
"permalink": "/r/Android/comments/n17u2/confession_of_a_nonapplefanboy_even_if_the/",
"name": "t3_n17u2",
"created": 1323127132,
"url": "http://www.businessinsider.com/apple-iphone-versus-samsung-galaxy-nexus-2011-12",
"author_flair_text": null,
"author": "FormulaT",
"created_utc": 1323101932,
"num_comments": 0,
"ups": 1
}
}
],
"after": null,
"before": null
}
},
{
"kind": "Listing",
"data": {
"modhash": "1jq62oyvwe15aaba7eb18b0b4363b567a00750766351e03dcc",
"children": [],
"after": null,
"before": null
}
}
]
What I'm trying to accomplish is to get just a few values out of this JSON, e.g. the title and the author. I'm using Jackson to handle the JSON, and the code I'm using looks like this:
URLConnection conn = redditURL.openConnection();
BufferedReader buf = new BufferedReader(new InputStreamReader(conn.getInputStream()));
ObjectMapper mapper = new ObjectMapper();
RedditComment comment = mapper.readValue(buf, RedditComment.class);
Iterator itr = comment.getData().getChildren().listIterator();
I created the RedditComment and other needed classes using the JSONGen website (http://jsongen.byingtondesign.com/). However, when parsing the JSON from the BufferedReader, Jackson throws this exception:
org.codehaus.jackson.map.JsonMappingException: Can not deserialize instance of com.test.RedditAPI.RedditComment out of START_ARRAY token
at [Source: java.io.BufferedReader#3ebfbbe3; line: 1, column: 1]
at org.codehaus.jackson.map.JsonMappingException.from(JsonMappingException.java:163)
at org.codehaus.jackson.map.deser.StdDeserializationContext.mappingException(StdDeserializationContext.java:219)
at org.codehaus.jackson.map.deser.StdDeserializationContext.mappingException(StdDeserializationContext.java:212)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromArray(BeanDeserializer.java:869)
at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:597)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2723)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1877)
at com.test.RedditAPI.main.returnRedditComment(Main.java:17)
Does anyone have any ideas? I've been scratching my head for a few hours now.
EDIT: Thanks to #Chin Boon and #Chris, I came up with the following (after switching to GSON):
Gson gson = new Gson();
List<RedditComment> comment = gson.fromJson(buf, new TypeToken<List<RedditComment>>() {}.getType());
List<RedditChildren> children = comment.get(1).getData().getChildren();
System.out.println(children.get(1).getData().getAuthor());
but that throws the following exception:
java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.test.RedditAPI.RedditChildren
Apologies if I'm being a bother, I've looked around the API and there isn't any reference of LinkedHashMaps, so I don't know why it's popping up here.
The problem is that the response is an array of entries. Try:
List<RedditComment> comment = mapper.readValue(buf,new TypeReference<List<RedditComment>>(){});
if you have not already invested too much time into Jackson, may i recommend you looking at GSON, here is a tutorial that should have you started.
Google GSON API maps your JSON string into a domain object.
http://java.sg/parsing-a-json-string-into-an-object-with-gson-easily/
Also, you can use a JSON parser to see your JSON.

Categories