Jackson: deserialize JSON extract deep attribute into parent class - java

I have some trouble wording my title, so if my question should be re-worded, I'd be happy to repost this question for clarification. :)
Problem: I have this JSON structure
{
"name": "Bob",
"attributes": {
"evaluation": {
"stats": [
{
"testDate": "2020-02-04",
"score": 50
},
{
"testDate": "2020-04-01",
"score": 90
},
{
"testDate": "2020-05-10",
"score": 85
}
],
"survey": {...}
},
"interests": {...},
"personality": [...],
"someRandomUnknownField": {...}
}
}
attributes is any random number of fields except for evaluation.stats that we want to extract out. I want to be able to deserialize into the following classes:
public class Person {
String name;
Map<String, Object> attributes;
List<Stat> stats;
}
public class Stat {
LocalDate date;
int score;
}
When I serialize it back to JSON, I should expect something like this:
{
"name": "Bob",
"attributes" : {
"evaluation": {
"survey": {...}
},
"interests" : {...},
"personality": {...},
"someRandomUnknownField": {...}
},
"stats": [
{
"testDate": "2020-02-04",
"score": 50
},
{
"testDate": "2020-04-01",
"score": 90
},
{
"testDate": "2020-05-10",
"score": 85
}
]
}
I could technically map the whole Person class to its own custom deserializer, but I want to leverage the built-in Jackson deserializers and annotations as much as possible. It's also imperative that stats is extracted (i.e., stats shouldn't also exist under attributes). I'm having trouble finding a simple and maintainable serialization/deserialization scheme. Any help would be appreciate!

I'm not sure if this meets your criterion for a simple and maintainable serialization/deserialization scheme, but you can manipulate the JSON tree to transform your starting JSON into the structure you need:
Assuming I start with a string containing your initial JSON:
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
JsonNode root = mapper.readTree(inputJson);
// copy the "stats" node to the root of the JSON:
ArrayNode statsNode = (ArrayNode) root.path("attributes").path("evaluation").path("stats");
((ObjectNode) root).set("stats", statsNode);
// delete the original "stats" node:
ObjectNode evalNode = (ObjectNode) root.path("attributes").path("evaluation");
evalNode.remove("stats");
This now gives you the JSON you need to deserialize to your Person class:
Person person = mapper.treeToValue(root, Person.class);
When you serialize the Person object you get the following JSON output:
{
"name" : "Bob",
"attributes" : {
"evaluation" : {
"survey" : { }
},
"interests" : { },
"personality" : [ ],
"someRandomUnknownField" : { }
},
"stats" : [ {
"score" : 50,
"testDate" : "2020-02-04"
}, {
"score" : 90,
"testDate" : "2020-04-01"
}, {
"score" : 85,
"testDate" : "2020-05-10"
} ]
}
Just to note, to get this to work, you need the java.time module:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.11.3</version>
</dependency>
And you saw how this was registered in the above code:
mapper.registerModule(new JavaTimeModule());
I also annotated the LocalDate field in the Stat class, as follows:
#JsonProperty("testDate")
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd")
private LocalDate date;
Very minor note: In your starting JSON (in the question) you showed this:
"personality": [...],
But in your expected final JSON you had this:
"personality": {...},
I assumed this was probably a typo, and it should be an array, not an object, in both cases.

Related

How to extract nested json values by using spring boot api call

I have the following json.
[
{
"id": 1,
"footwearList": [
{
"id": 1,
"name": "sandals",
"category": "men"
},
{
"id": 3,
"name": "sandals",
"category": "women"
}
],
"clothingList": [
{
"id": 1,
"name": "t-shirt",
"category": "men"
},
{
"id": 3,
"name": "tshirt",
"category": "women"
}
]
},
{
"id": 2,
"footwearList": [
{
"id": 2,
"name": "shoes",
"category": "men"
},
{
"id": 4,
"name": "shoes",
"category": "women"
}
],
"clothingList": [
{
"id": 2,
"name": "shirt",
"category": "men"
},
{
"id": 4,
"name": "shirt",
"category": "women"
}
]
}
]
Fetched this json from api call from controller and wanted to fetch nested values like (footwearlist, clothinglist) from the json through api call from controller.
And if found then again fetching by filtering category.
I tried using JsonPath with added dependency in pom.xml
Dependency:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.7.0</version>
</dependency>
Tried to fetch the nested json but it didn,t work.
public List<Store> getCategory(){
List<Store> footwear = JsonPath.read(json, "$..footwear");
}
There's no need to introduce a new dependency, there are several possible way to solve this problem using Jackson.
Jackson Streaming API
If, for some reason, you don't want to materialize all data as objects and want to iterate over it. Then generate a JsonNode from your JSON using ObjectMapper.readTree(), and examine the field-names of each nested node. For that, you can use JsonNode.fields(). If the matching field was encountered - grab the data.
To check whether a field is matching you can make use of the regular expression. Here's regex-based predicate that would check if the given string contains substring "footwear":
public static final Predicate<String> FOOTWEAR = Pattern.compile("footwear").asPredicate();
That's how the code for iterating over the tree having the structure you shown might look like:
String json = // incoming JSON
ObjectMapper mapper = new ObjectMapper();
List<Item> items = StreamSupport.stream(mapper.readTree(json).spliterator(), false)
.<JsonNode>mapMulti((node, consumer) ->
node.fields().forEachRemaining(entry -> {
if (FOOTWEAR.test(entry.getKey())) entry.getValue().forEach(consumer);
})
)
.map(StreamingNestedObjects::nodeToItem)
.toList();
items.forEach(System.out::println);
Output:
Item{id=1, name='sandals', category='men'}
Item{id=3, name='sandals', category='women'}
Item{id=2, name='shoes', category='men'}
Item{id=4, name='shoes', category='women'}
Assuming that Item class look like this:
public class Item {
private int id;
private String name;
private String category;
// getters, setters, etc.
}
But the incoming JSON is not very massive, I would advise to make of the Jackson's Data binding.
Data Binding
Consider the following class Store:
public class Store {
private int id;
private Map<String, List<Item>> items = new HashMap<>();
#JsonAnySetter
public void readStore(String key, List<Item> value) {
items.put(key, value);
}
#JsonAnyGetter
public Map<String, List<Item>> writeStore(String key, List<Item> value) {
return items;
}
// no-args constructor, getter and setter for id property, etc.
}
One of its properties is a map of type Map<String, List<Item>> containing lists of items. This map can be serialized/deserialized via #JsonAnySetter and #JsonAnyGetter.
That how you can parse your incoming JSON into a list of Store instances:
ObjectMapper mapper = new ObjectMapper();
List<Store> stores = mapper.readValue(json, new TypeReference<>() {});
Then iterate over the list, check the Keys of the Maps and pick the stores you need.

Java GSON Json partial parsing

Say I have a JSON object representing an entity (can be any entity) like so:
{
"entity_id": "1",
"entity_name": "employee",
"entity_json": {
"employee_id": "e01",
"employee_name": "john",
"employee_phone_numbers": [
"1234567",
"8765433"
]
}
}
Note that entity_json can represent different entities having different structures as long as it is a valid JSON. For example, the following is another entity's representation:
{
"entity_id": "1",
"entity_name": "invoice",
"entity_json": {
"invoice_id": "1011",
"items": {
"item_id": "1",
"quantity": "3",
"price": "$100"
},
"date": "01-01-2020",
"customer": {
"id": "3",
"address": {
"street": "some_street",
"country": "CZ",
...
}
}
}
}
I want to be able to partially parse this JSON into an Entity POJO using Gson in Java. That is, I'll have an entity POJO like the one shown below:
public class Entity {
private String entity_id;
private String entity_name;
private String entity_json; // <-- entity_json is a String
// getters and setters
}
/*
* entity_json (for employee) = "{ \"employee_id\": \"1\", \"employee... }"
* entity_json (for invoice) = "{ \"invoice_id\": \"1011\", \"items... }"
*/
and I'm planning on performing any operation on entity_json using JsonPath.
Is there any way I can achieve this WITHOUT having to explicitly set entity_json in the JSON structure as a string with escapes?
Any help is appreciated here. Thanks!
You can avoid using a String for your entity_json by using Gson's JsonObject.
Here is my revised Entity class:
import com.google.gson.JsonObject;
public class MyEntity {
private String entity_id;
private String entity_name;
private JsonObject entity_json;
// getters and setters not shown
}
Then you can populate instances as follows:
MyEntity myEntityOne = new Gson().fromJson(JSON_ONE, MyEntity.class);
MyEntity myEntityTwo = new Gson().fromJson(JSON_TWO, MyEntity.class);
System.out.println(myEntityOne.getEntity_json());
System.out.println(myEntityTwo.getEntity_json());
In the above code, JSON_ONE and JSON_TWO are just strings containing the two sample JSONs from your question.
The console prints out the following (snipped for brevity):
{"employee_id":"e01","employee_name":"john","employee_phone_numbers":["1234567","8765433"]}
{"invoice_id":"1011","items":{"item_id":"1","quantity":"3","price":"$100"},"date":"01-01-2020"...
You can, of course, now use Gson to further manipulate each entity_json field as needed, since each one is itself a valid JSON object.

Java collections to JSON

My program has to output following JSON format:
{ "success": {
"code": 1,
"desc": "success" },
"response": {
"res1": [
{
"Item": "item1",
"Description": [
{
"desc": "ad1",
"active": true,
"details": [
{
"Type": "Type1",
"Count": 2,
"Status": true
},
{
"Type": "TYpe2",
"Count": 3,
"Status": false
},
]
},
{
"desc": "item2",
"active": true,
"details": [
{
"Type": "Type1",
"Count": 4,
"Active": true
}
]
},
{
"Item": "item2",
"Description": [
{
"desc": "d2",
"Active": true,
"details": [
{
"Type": "Type3",
"Count": 6,
"Active": true
},
]
},
]
}
]
}
}
I have wrote following to create this json format
HashMap<String, HashMap<String, String>> m1 = new HashMap<String, HashMap<String, String>>();
HashMap<String, String> m2 = new HashMap<String, String>();
HashMap<String, ArrayList<HashMap<String, String>>> m3 = new HashMap<String, ArrayList<HashMap<String, String>>>();
For creating the mentined JSON format, I tried to put m3 and m2 in m1. But since these are not with same type, it won't allow to put.
I am using GSON to convert the collection to JSON.
Gson gson = new Gson();
String json1 = gson.toJson(m1);
My Question is: For creating that JSON format, which collection in Java I have to use?
The best way to deal with a complex JSON structure like that is to create a class with the same structure with properties that represent the underlying arrays as lists. Then, you can use gson to convert the object(s) to JSON in a straightforward way.
In other words, instead of trying to fit the structure into a combination of complex collections or existing Java classes, create a class that is an exact representation of the data, populate it, and then convert it to JSON.
Why don't you let the code generated for you?
After the pojos are created you can of course change the code to tailor your needs.
Create a ResponseWrapper class and build your json accordingly.
Sample code:
public class ResponseWrapper<T>{
private Map<String, T> wrappedObjects = new HashMap<String, T>();
public ResponseWrapper() {
}
public ResponseWrapper(String name, T wrappedObject) {
this.wrappedObjects.put(name, wrappedObject);
}
..
..
Your json can be built like this:
ResponseWrapper response = new ResponseWrapper<ResponseWrapper>();
Items[] item = ....
//item can itself have Description, etc.
response.set("res1", (ArrayList) items);
Thanks for notepad++ and its json view plugin, i can read your json data in proper format. (although your data missing a few brackets)
In my oponion, json structure can be converted into Map but i personally don't like that idea much.
The other way to do is using Java POJO to define your JSON object and then convert it to json. Excellent example here
OK. We will check your data and create Java POJO for it. It's simple and should be something like this
public class YourJsonObj {
public SuccessObj success;
public ResponseObj response;
}
where SuccessObj and ResponseObj are another structure Classes like this:
public class SuccessObj {
public Integer code;
public String desc;
}
public class ResponseObj {
public List<ResObj> res1;
}
Another sub class appear: ResObj. All you have to do is continue define it:
public class ResObj {
public String Item; // << this property's name does't make java happy
public List<DescriptionObj> Description // << this property's name does't make java happy
}
Continue to do this definition till the end of your data. And you got it.
My suggestion is to replicate the same hierarchy of the fields in your JSON example.
By the Google Gson library you can convert (in 2 lines of code) your Java object to a JSON object. Please check the following example (from the user guide of the relative project).
class BagOfPrimitives {
private int value1 = 1;
private String value2 = "abc";
private transient int value3 = 3;
BagOfPrimitives() {
// no-args constructor
}
}
// Serialization
BagOfPrimitives obj = new BagOfPrimitives();
Gson gson = new Gson();
String json = gson.toJson(obj);
// ==> json is {"value1":1,"value2":"abc"}

Jackson ignore properties using the same class

my question is about the fact that i want to use the same class to deserialize and re-serialize two different Jsons. I try to explain better.
I've these Jsons:
//JSON A
{
"flavors": [
{
"id": "52415800-8b69-11e0-9b19-734f1195ff37",
"name": "256 MB Server",
"ram": 256,
"OS-FLV-DISABLED:disabled":true
"links": [
{
"rel": "self",
"href": "http://www.myexample.com"
},
{
"rel": "bookmark",
"href":"http://www.myexample.com"
}
]
},
...
}
//JSON B
{
"flavors": [
{
"id": "52415800-8b69-11e0-9b19-734f1195ff37",
"name": "256 MB Server",
"links": [
{
"rel": "self",
"href": "http://www.myexample.com"
},
{
"rel": "bookmark",
"href":"http://www.myexample.com"
}
]
},
...
}
As you can see JSON B has all the fields of JSON A except "ram" and
"OS-FLV-DISABLED:disabled". The classes i used are the following:
public class Flavor {
private String name;
private List<Link> links;
private int ram;
private boolean OS_FLV_DISABLED_disabled;
//constructor and getter/setter
}
#XmlRootElement
public class GetFlavorsResponse {
private List<Flavor> flavors;
//constructor and getter/setter
}
Moreover just above the getter method isOS_FLV_DISABLED_disabled i've put the annotation #XmlElement(name = "OS-FLV-DISABLED:disabled")
otherwise Jackson doesn't recognize this property.
Here is the scheme of the situation:
When i receive JSON A there are no problems, JSON resultant is again JSON A; but when i receive JSON B the result of the process deserialization-serialization is:
//JSON C
{
"flavors": [
{
"id": "52415800-8b69-11e0-9b19-734f1195ff37",
"name": "256 MB Server",
"ram": 0,
"OS-FLV-DISABLED:disabled":false
"links": [
{
"rel": "self",
"href": "http://www.myexample.com"
},
{
"rel": "bookmark",
"href":"http://www.myexample.com"
}
]
},
...
}
Now as first thing i thought that Jackson sets class properties that was not in Json with
their default values, that is, 0 and false respectively for "ram" and
"OS-FLV-DISABLED:disabled". So i've put the annotation
#JsonSerialize(include=JsonSerialize.Inclusion.NON_DEFAULT)
just above Flavor class. This works but the problem is that when i receive JSON A in which "ram" and "OS-FLV-DISABLED:disabled" have as values 0 and false (possible situation), the result of the process mentioned above is JSON B since these two fields are ignored.
So established that this is not the solution for my problem, i read that some people suggest to use #JsonView or #JsonFilter but i don't understand how to apply these Jackson features in this case.
I hope i was clear and thanks you in advance for your help.
One thing you can try is that make your ram and OS_FLV_DISABLED_disabled as Integer and Boolean types respectively. By this if no values come in json for these two properties then they will be null. And use this annotation #JsonInclude(Include.NON_NULL) to avoid serializing null properties.

Converting a specific JSON to Java Object

I am able to convert simple Json into java objects using Google Gson. The issue is I am not able to convert this specific type of response to a java object. I think this is due to the fact that I am not sure how to actually model the java classes for it. The following is the Json:
{
"List": {
"Items": [
{
"comment": "comment",
"create": "random",
"ID": 21341234,
"URL": "www.asdf.com"
},
{
"comment": "casdf2",
"create": "rafsdfom2",
"ID": 1234,
"Url": "www.asdfd.com"
}
]
},
"related": {
"Book": "ISBN",
"ID": "id"
}}
I haven't worked with Gson specifically, but I have worked with JSON / Java conversion in Jersey and JAXB.
I would say that the easiest way to translate the JSON text is to map every {..} to a Class and [..] to a List.
For example:
Class Data {
HistoryListClass HistoryList;
Relation related;
}
Class HistoryListClass {
List<History> history;
}
Class History {
String comment;
String create;
int ID;
String ttURL;
}
Class Relation {
String book;
String ID;
}
See also:
Converting JSON to Java
The "HistoryList" element in the JSON code should probably be written "historyList", and just looking at the code given, I suggest you change it to contatain the list of "History" objects directly.

Categories