How to parse complex nested JSON in java? - java

I have a complex nested Json
It has a body similar to this:
{
staus: "Success",
id: 1,
data: [{'Movie':'kung fu panda','% viewed': 50.5},{'Movie':'kung fu panda 2','% viewed':1.5}],
metadata: {'filters':['Movie', 'Percentage Viewed'], 'params':{'content':'Comedy', 'type': 'Movie'}}
}
The only field I care about is data, and metadata is usually an even more complex/nested field. I was trying to map this to:
#JsonIgnoreProperties(ignoreUnknown = true)
class ResponseData{
public Data[] data;
#JsonIgnoreProperties(ignoreUnknown = true)
class Data{
public String Movie;
public double viewed;
}
}
I was looking at Jackson as an option and writing my own serializer and use JsonIgnore to discard the metadata but can't get around it.
Any suggestion on how this could be done?

You can use jackson-utils
public class Foo {
public static void main(String... args) {
ResponseData responseData1 = new ResponseData(
1,
"Success",
new ResponseData.Data[] {
new ResponseData.Data("kung fu panda", 50.5),
new ResponseData.Data("kung fu panda 2", 1.5) },
new ResponseData.Metadata(
new HashSet<>(Arrays.asList("Movie", "Percentage Viewed")),
new ResponseData.Metadata.Params("Comedy", "Movie"))
);
String json = JacksonUtils.prettyPrint().writeValue(responseData1);
System.out.println(json);
ResponseData responseData2 = JacksonUtils.readValue(json, ResponseData.class);
}
}
class ResponseData {
private int id;
private String status;
private Data[] data;
private Metadata metadata;
public ResponseData() {
}
public ResponseData(int id, String status, Data[] data, Metadata metadata) {
this.id = id;
this.status = status;
this.data = data;
this.metadata = metadata;
}
public static class Data {
#JsonProperty("Movie")
private String movie;
#JsonProperty("% viewed")
private double viewedPercents;
public Data() {
}
public Data(String movie, double viewedPercents) {
this.movie = movie;
this.viewedPercents = viewedPercents;
}
}
public static class Metadata {
private Set<String> filters;
private Params params;
public Metadata() {
}
public Metadata(Set<String> filters, Params params) {
this.filters = filters;
this.params = params;
}
public static class Params {
private String content;
private String type;
public Params() {
}
public Params(String content, String type) {
this.content = content;
this.type = type;
}
}
}
}
Console output:
{
"id" : 1,
"status" : "Success",
"data" : [ {
"Movie" : "kung fu panda",
"% viewed" : 50.5
}, {
"Movie" : "kung fu panda 2",
"% viewed" : 1.5
} ],
"metadata" : {
"filters" : [ "Movie", "Percentage Viewed" ],
"params" : {
"content" : "Comedy",
"type" : "Movie"
}
}
}
P.S. As an alternative, there is another util gson-utils with the same syntax.

Related

How to model JSON response with Jackson in Java?

I am try to model an Api response using Jackson. The id will be the same type in all but the body will be different types.
An example response would be:
{
"responses": [
{
"id": "jobTitle",
"body": {
"jobTitle": "Software Engineer"
}
},
{
"id": "thumbnailPhoto",
"body": "base 64 bit string"
}
]
}
I have the following implementation. Is this the correct approach? If the type for body returns as a string, would the JobTitle be ignored/ null?
#JsonIgnoreProperties(ignoreUnknown = true)
public class Response
{
#JsonProperty("id")
private String id;
#JsonProperty("body")
private String photo;
#JsonProperty("body")
private JobTitle jobTitle;
// getters and setters
}
I'm no expert in this area, but I would like to share my answer here.
I don't know why you design the JSON string as an array of responses for your original question. I would suggest a better design to be a single instance of "Response" object as below:
{
"id":"response id",
"jobTitle":"title",
"img":"img b64 string"
}
Just leave the field null if not exists.
But if you insist on using the origin design, below code below coding can be achieved, but the JSON string need small changes to add "type" info Tutorial from Baeldung.
[ {
"id" : "1",
"body" : {
"type" : "jobTitle",
"jobTitle" : "job title"
}
}, {
"id" : "2",
"body" : {
"type" : "img",
"data" : "xxxxx"
}
} ]
Java coding as below:
package org.example.test4;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import java.util.ArrayList;
public class TestApp {
public static class Response<X extends Body> {
private String id;
private X body;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public X getBody() {
return body;
}
public void setBody(X body) {
this.body = body;
}
}
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = JobTitle.class, name = "jobTitle"),
#JsonSubTypes.Type(value = IMG.class, name = "img")
})
public static abstract class Body{}
public static class JobTitle extends Body{
private String jobTitle;
public String getJobTitle() {
return jobTitle;
}
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
}
public static class IMG extends Body{
private String data;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT);;
JobTitle jt = new JobTitle();
jt.setJobTitle("job title");
System.out.println(om.writeValueAsString(jt));
IMG img = new IMG();
img.setData("xxxxx");
System.out.println(om.writeValueAsString(img));
ArrayList<Response<?>> rs = new ArrayList<Response<?>>();
Response<JobTitle> r1 = new Response<JobTitle>();
r1.setId("1");
r1.setBody(jt);
rs.add(r1);
Response<IMG> r2 = new Response<IMG>();
r2.setId("2");
r2.setBody(img);
rs.add(r2);
System.out.println(om.writeValueAsString(rs));
}
}

How to convert a POST API call with a JSON body to a java class in Springboot?

i'm passing a POST request with raw JSON body which holds the configuration details, i want to save it as a java class so i can distribute the data using getters to relevant classes that need the config details specified for them.This is the body of my request
{
"aisles" : 2,
"sections" : 2,
"shelves" : 1,
"packagingAreas": [ "a1.3", "a2.3" ],
"workers" : [
{
"name" : "rem",
"location" : "a1.1",
"capacity" : 20
}
],
"items" : [
{
"id" : "mars",
"name" : "Mars",
"supplier" : "Nestle",
"weight" : 1
},
{
"id" : "kitkat",
"name" : "Kit Kat",
"supplier" : "Nestle",
"weight" : 1
},
{
"id" : "dd",
"name" : "Double Decker",
"supplier" : "Nestle",
"weight" : 1
}
]
}
and i want to the details of that body into my config.java class, this is the config.java class
public class Config {
private static String aisles;
private static String sections;
private static String shelves;
private static String packagingAreas[];
private static ArrayList<Worker> workers;
private static ArrayList<Item> items;
public static String getAisles() {
return aisles;
}
public static String getSections() {
return sections;
}
public static String getShelves() {
return shelves;
}
public static String[] getPackagingAreas() {
return packagingAreas;
}
public static ArrayList<Worker> getWorkers() {
return workers;
}
public static ArrayList<Item> getItems() {
return items;
}
}
And i have modelled the worker and item classes with the same variables as in the json configuration file, is there a direct way to convert this JSON file to a class? if not what other methods can i try?
Thanks in advance!
edit- This is the endpoint i have created, using #Rest Controller
#RequestMapping(value ="/config", method = RequestMethod.POST)
public void configure() {
//i want to do the conversion here
}
Update the controller as follows
#RequestMapping(value ="/config", method = RequestMethod.POST)
public void configure(#RequestBody Config config) {
//your json is converted to config java object
}
Update your Config class
public class Config {
private String aisles;
private String sections;
private String shelves;
private String packagingAreas[];
private ArrayList<Worker> workers;
private ArrayList<Item> items;
public void setAisles(String aisles) {
this.aisles = aisles;
}
public void setSections(String sections) {
this.sections = sections;
}
public void setShelves(String shelves) {
this.shelves = shelves;
}
public void setPackagingAreas(String[] packagingAreas) {
this.packagingAreas = packagingAreas;
}
public void setWorkers(ArrayList<Worker> workers) {
this.workers = workers;
}
public void setItems(ArrayList<Item> items) {
this.items = items;
}
public String getAisles() {
return aisles;
}
public String getSections() {
return sections;
}
public String getShelves() {
return shelves;
}
public String[] getPackagingAreas() {
return packagingAreas;
}
public ArrayList<Worker> getWorkers() {
return workers;
}
public ArrayList<Item> getItems() {
return items;
}
}
Explanation
By default spring boot comes with several HttpMessageConverters enabled. One of them is MappingJacksonHttpMessageConverter which converts your json to java object.
See this http message converter

Get the value of items of a Json with Java Spring Boot

I'm trying to extract API data from the Json file below.
I want to retrieve the "name" of each "item".
Once the "name" is retrieved, I want to create a new Json that will contain :
{name: "toto", name: "titi"....}
The goal is then to create an API on my side which on a call from http://localhost/getitems will return the result of the Json created.
I'm new to Java and Spring Boot, so if you think there is a code that is easier, let me know, i hope you can help me to create that new Json file easily. Thanks !
// Json File (it has been reduced, more than 700 name are present)
{
"kind": "Space",
"apiVersion": "v1",
"metadata": {
"selfLink": "something",
"resourceVersion": "something"
},
"items": [
{
"metadata": {
"name": "projet1"
}
},
{
"metadata": {
"name": "com-cicd"
}
}
]
}
// TestGet.java Class
public static NameSpaceJson getPostWithCustomHeaders(String DebutUrl, String MilieuUrl, String ParamUrl) {
String url = DebutUrl.concat(MilieuUrl).concat(ParamUrl);
String Bearer = "...";
// create headers & template
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
// set `accept` header for the type of response
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
// set custom header, bearer here too
headers.set("x-request-source", "desktop");
headers.set("Authorization", "Bearer "+Bearer);
// build the request
#SuppressWarnings({ "rawtypes", "unchecked" })
HttpEntity request = new HttpEntity(headers);
// use `exchange` method for HTTP call, this one permits us to use bearer for auth
ResponseEntity<NameSpaceJson> response = restTemplate.exchange(url, HttpMethod.GET, request, NameSpaceJson.class, 1);
if(response.getStatusCode() == HttpStatus.OK) {
return response.getBody();
} else {
return null;
}
}
// The name in the same file
public static void main(String[] args) {
TestGet.disableSSLCertificateChecking();
NameSpaceJson resultresponse = getPostWithCustomHeaders("https...","api","names");
// Long response = resultresponse.getValue().getId();
List<Item> response = resultresponse.getItems();
String test = GenerateNewJsonNameSpace.createJsonContent(response);
System.out.println(test);
}
//NameSpaceJson.java File
package com.example.consumingrest;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class NameSpaceJson {
private String kind;
private String apiVersion;
private List<Item> items;
public String getKind() {
return kind;
}
public void setKind(String kind) {
this.kind = kind;
}
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public List<Item> getItems() {
return items;
}
public void setItems(List<Item> items) {
this.items = items;
}
}
//Metadata.java
package com.example.consumingrest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Metadata {
private String name;
private String creationTimestamp;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getCreationTimestamp() {
return creationTimestamp;
}
public void setCreationTimestamp(String creationTimestamp) {
this.creationTimestamp = creationTimestamp;
}
}
//Item.java
package com.example.consumingrest;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class Item {
Metadata metadata;
public Metadata getMetadata() {
return metadata;
}
public void setMetadata(Metadata metadata) {
this.metadata = metadata;
}
}
// GenerateNewJsonNameSpace ( this is what i have tried.. but i'm sure we can do really better.. )
package com.example.consumingrest;
import java.util.List;
public class GenerateNewJsonNameSpace {
public static String createJsonContent(List<Item> ListOfNameSpace) {
if(ListOfNameSpace.isEmpty()) {
return null;
}else {
String LeJson;
LeJson = "{";
for(int i = 0; i < ListOfNameSpace.size(); i++) {
LeJson.concat(ListOfNameSpace.get(i).getMetadata().getName());
LeJson.concat(", \n");
}
LeJson.concat("}");
return LeJson;
}
}
}
you can use a library named Gson, which is created by google specifically for handling the JSON data.
All you need to do is create a new Gson object and parse the JSON with it.
You can do in just couple of lines
String jsonString = "{ \"kind\": \"Space\", \"apiVersion\": \"v1\", \"metadata\": { \"selfLink\": \"something\", \"resourceVersion\": \"something\" }, \"items\": [ { \"metadata\": { \"name\": \"projet1\" } }, { \"metadata\": { \"name\": \"affeccom-cicd\" } } ] }";
JsonObject data = new Gson().fromJson(jsonString, JsonObject.class);
JsonArray names = data .get("items").getAsJsonArray();
for(JsonElement element : names){
JsonObject object = element.getAsJsonObject();
System.out.println(object.get("metadata").getAsJsonObject().get("name").getAsString());
}

Nested JSON objects from Spring RestClient and Jackson

I have a rest client that will be getting JSON back from an endpoint. I would like to just get what is in the data[].
{
"responseStatus": "SUCCESS",
"responseDetails": {
"limit": 1000,
"offset": 0,
"size": 2,
"total": 2
},
"data": [
{
"id": "00P000000001M01",
"name__v": "Foo",
"status__v": [
"active__v"
],
"abbreviation__c": "F170053",
"internal_name__c": "Foo",
"therapeutic_area__c": [
"neurology__c"
],
"external_id__v": "84",
"generic_name__c": "Foo",
"scientific_name__c": null
},
{
"id": "00P000000001N01",
"name__v": "Bar",
"status__v": [
"active__v"
],
"abbreviation__c": "B333334",
"internal_name__c": "Bar",
"therapeutic_area__c": [
"bone_muscle_joint__c"
],
"external_id__v": "101",
"generic_name__c": "Bar",
"scientific_name__c": null
}
]
}
Because I will make other calls which will return different fields in the data[], I wanted to map each type to a POJO so I used #JSONProperty
#JsonIgnoreProperties(ignoreUnknown = true)
public class Product extends VBase{
private String fNumber;
private String genericName;
private String scientificName;
private String therapeuticArea;
public String getFNumber() {
return fNumber;
}
#JsonProperty("abbreviation__c")
private void unpackFNumber(Map<String,Object> abbreviation__c){
fNumber = ((Map<String,Object>)abbreviation__c.get("data")).get("abbreviation__c").toString();
}
public void setLyNumber(String fNumber) {
this.fNumber = fNumber;
}
public String getGenericName() {
return genericName;
}
#JsonProperty("generic_name__c")
private void unpackGenericName(Map<String,Object> generic_name__c){
genericName = ((Map<String,Object>)generic_name__c.get("data")).get("generic_name__c").toString();
}
public String getScientificName() {
return scientificName;
}
#JsonProperty("scientific_name__c")
private void unpackScientificName(Map<String,Object>sName){
scientificName = ((Map<String,Object>)sName.get("data")).get("scientific_name__c").toString();
}
public String getTherapeuticArea() {
return therapeuticArea;
}
#JsonProperty("therapeutic_area__c")
private void unpackTheraputicArea(Map<String,Object>tArea){
therapeuticArea=((Map<String,Object>)tArea.get("data")).get("therapeutic_area__c").toString();
}
}
I have tried various ways of just getting the data[].
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
RestTemplate restTemplate = new RestTemplate(clientHttpReq);
ResponseEntity<Product[]> response = restTemplate.exchange(url, HttpMethod.GET, request,Product[].class);
//ResponseEntity<Product[]> response = restTemplate.postForEntity(url,request,Product[].class ,parmMap);
What I have been trying to avoid is making a ResponseDetail POJO with a Data[] as a field. I know it will work but because the fields in the data[] will change based on the endpoint.
Can I use a wrapper class with a List to represent the data[] as all of the POJO will extend VBase?
Here is the exception:
Exception in thread "main" org.springframework.web.client.RestClientException: Error while extracting response for type [class [Lcom.lilly.models.Product;] and content type [application/json;charset=UTF-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize instance of `com.lilly.models.Product[]` out of START_OBJECT token; nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `com.lilly.models.Product[]` out of START_OBJECT token
at [Source: (PushbackInputStream); line: 1, column: 1]
Declaring a POJO class as below code, and then you can get data property from the POJO.
#Data
public class Example {
private String responseStatus;
private ResponseDetails responseDetails;
private List<Data> data;
#lombok.Data
public static class ResponseDetails {
private int limit;
private int offset;
private int size;
private int total;
}
#lombok.Data
public static class Data {
private String id;
private String name__v;
private String abbreviation__c;
private String internal_name__c;
private String external_id__v;
private String generic_name__c;
private Object scientific_name__c;
private List<String> status__v;
private List<String> therapeutic_area__c;
}
#lombok.Data
#JsonIgnoreProperties(ignoreUnknown = true)
public static class Product {
#JsonProperty("name__v")
private String fNumber;
#JsonProperty("generic_name__c")
private String genericName;
#JsonProperty("scientific_name__c")
private String scientificName;
#JsonProperty("therapeutic_area__c")
private List<String> therapeuticArea;
}
public static void main(String[] args) {
String jsonResult = "{\"responseStatus\":\"SUCCESS\",\"responseDetails\":{\"limit\":1000,\"offset\":0,\"size\":2,\"total\":2},\"data\":[{\"id\":\"00P000000001M01\",\"name__v\":\"Foo\",\"status__v\":[\"active__v\"],\"abbreviation__c\":\"F170053\",\"internal_name__c\":\"Foo\",\"therapeutic_area__c\":[\"neurology__c\"],\"external_id__v\":\"84\",\"generic_name__c\":\"Foo\",\"scientific_name__c\":null},{\"id\":\"00P000000001N01\",\"name__v\":\"Bar\",\"status__v\":[\"active__v\"],\"abbreviation__c\":\"B333334\",\"internal_name__c\":\"Bar\",\"therapeutic_area__c\":[\"bone_muscle_joint__c\"],\"external_id__v\":\"101\",\"generic_name__c\":\"Bar\",\"scientific_name__c\":null}]}";
ObjectMapper objectMapper = new ObjectMapper();
try {
Example example = objectMapper.readValue(jsonResult, new TypeReference<Example>() {
});
//you can get data[] via example.getData()
System.out.println(objectMapper.writeValueAsString(example.getData()));
List<Product> products = objectMapper.readValue(objectMapper.writeValueAsString(example.getData()), new TypeReference<List<Product>>() {
});
//you can also parse data[] as List<Product>
System.out.println(JSONObject.toJSONString(products));
} catch (IOException e) {
e.printStackTrace();
}
}
}

Realm can't create Nested Objects from Json

I've been trying to create a nested RealmObject using a json but it only creates the first Object and not the nested ones. I would appreciate a help on this.
my Realm classes:
Content.java
public class Content extends RealmObject {
private String uuid;
RealmList<ContentDetailModel> ContentDetail;
public Content() {
super();
this.uuid = UUID.randomUUID().toString();
}
public String getUuid() {
return uuid;
}
public RealmList<ContentDetailModel> getContentDetails() {
return ContentDetail;
}
public void setContentDetails(RealmList<ContentDetailModel> contentDetails) {
this.ContentDetail = contentDetails;
}
}
ContentDetailModel.java:
public class ContentDetailModel extends RealmObject {
String FileName;
String ContentTypeID;
RealmList<ContentDetailMetadataModel> ContentDetailMetadata;
RealmResults<Content> content = null;
public String getFileName() {
return FileName;
}
public void setFileName(String fileName) {
FileName = fileName;
}
public String getContentTypeID() {
return ContentTypeID;
}
public void setContentTypeID(String contentTypeID) {
ContentTypeID = contentTypeID;
}
public RealmList<ContentDetailMetadataModel> getContentDetailMetadata() {
return ContentDetailMetadata;
}
public void setContentDetailMetadata(RealmList<ContentDetailMetadataModel> contentDetailMetadataz) {
this.ContentDetailMetadata = contentDetailMetadataz;
}
}
and the rest of nested classes are like these. my Json string is as follows:
"
{
"Content":{
"ContentDetail":[
{
"FileName":"test.mp3",
"ContentTypeID":3,
"ContentDetailMetadata":{
"Metadata":[
{
"ID":2,
"Value":"2017-08-02 09:40:30"
},
{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
]
}
},
{
"FileName":"2.jpg",
"ContentTypeID":2,
"ContentDetailMetadata":[
{
"Metadata":{
"ID":2,
"Value":"2017-08-02 09:40:30"
}
},
{
"Metadata":{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
}
]
}
]
}
}
"
and the code I use to do it is :
realm.createObjectFromJson(json)
{
"Content":{
"ContentDetail":[
{
"FileName":"test.mp3",
"ContentTypeID":3,
"ContentDetailMetadata":[{
"Metadata":[
{
"ID":2,
"Value":"2017-08-02 09:40:30"
},
{
"ID":1,
"Value":"35.73876557934912,51.50785446166992"
}
]
}]
},
Translates to:
public class Root extends RealmObject {
private Content Content;
}
public class Content extends RealmObject {
private RealmList<ContentDetail> ContentDetail;
#LinkingObjects("Content")
private final RealmResults<Root> roots = null;
}
public class ContentDetail extends RealmObject {
private String FileName;
private long ContentTypeID;
//private ContentDetailMetadata ContentDetailMetadata;
private RealmList<ContentDetailMetadata> ContentDetailMetadata;
#LinkingObjects("ContentDetail")
private final RealmResults<Content> contents = null;
}
public class ContentDetailMetadata extends RealmObject {
private RealmList<Metadata> Metadata;
#LinkingObjects("ContentDetailMetadata")
private final RealmResults<ContentDetail> contentDetails = null;
}
public class Metadata extends RealmObject {
private long ID;
private String Value;
#LinkingObjects("Metadata")
private final RealmResults<ContentDetailMetadata> contentDetailMetadatas = null;
}
If your schema doesn't look like that, then createOrUpdateFromJson() won't work.
Personally I would advise against using this schema though, it's pretty bad as a Realm schema. It's advisable to parse the JSON and then map it into a schema that makes more sense!
It looks like your JSON has put all fields for the Content object under the Context JSON object instead of directly under the top-level object where it should be.
If you do this, it should work:
realm.createObjectFromJson(Content.class, json.getJSONObject("Content"));

Categories