spring boot jackson generate dynamic property name with hierarchical levels - java

How to apply dynamic property name with counter in it.?
I am building a spring boot rest api that returns the following response object.
Response object is a structure with hierarchical levels. Instead of showing all the "levels" property name as default "levels", i want to dynamically put the level number to it as explained in the below sample json.
public class Root {
private String id;
private List<Level> levels;
}
public class Level {
private String name;
private List<Level> levels;
}
current json output:
{
"id" :"testid",
"levels" : [
{
"name" :"test1"
"levels" : [
{
"name": "test3"
"levels" : []
}
}
Sample expected json:
{
"id" :"testid",
"level1" : [
{
"name" :"test1"
"level2" : [
{
"name": "test3"
"level3" : []
}
}

i solved it as follows, by adding a field that contains current level and appending that to the field name.
public class Root {
private String id;
#JsonIgnore
private List<Level> levels;
#JsonIgnore
private int level;
#JsonAnyGetter
public Map<String, List<Level>> any() {
return Collections.singletonMap("level"+level, levels);
}
}
public class Level {
private String name;
private List<Level> levels;
#JsonIgnore
private int level;
#JsonAnyGetter
public Map<String, List<Level>> any() {
return Collections.singletonMap("level"+level, levels);
}
}

Related

Jackson: map "flat" json to Data class child objects

Project I'm working on has the following come through via MQ:
example.json
{
"templateName": "testTemplate",
"to": [
"support#test.com"
],
"cc": [
"testCc#test.com
],
"bcc": [
"testBcc#test.com
],
"from": "testFrom#test.com",
"subject": "testSubject",
"replacementValues": {
"replacementValue1": "lorem",
"replacementValue2": "ipsum"
},
"jsonObject": {
//omitted for brevity
}
}
And as is, it will map to the following object:
NotificationV1.java
public class NotificationV1 {
private String templateName;
private List<String> to;
private List<String> cc;
private List<String> bcc;
private String from;
private String subject;
private Map<String, String> replacementValues;
private Map<String, String> images;
private Object jsonObject;
//getters & setters omitted for brevity
using the following mapper:
//no special config
notificationMessage = new ObjectMapper().readValue(jsonMessage, EmailNotificationMessage.class);
As part of a project wide refactor, the data class above has been altered to instead look like this:
NotificationV2.java
public class NotificationV2 {
private EmailHeaders emailHeaders;
private TemplateData templateData;
//getters and setters omitted
EmailHeaders.java
public class EmailHeaders {
private String from;
private List<String> toAddresses;
private List<String> ccAddresses;
private List<String> bccAddresses;
private String subject;
//getters and setters omitted
TemplateData.java
public class TemplateData {
private String templateName;
private Map<String, String> replacementValues;
private Map<String, String> images;
private Object jsonObject;
//getters and setters omitted
Naturally, the existing mapping throws errors around unrecognised properties in the json vs. the new object; cant map templateNAme, found emailHeaders and templateData, and so on. I cant change the structure of the json in order to fit the new object but havent found a resource that demonstrates a use case like the above for mapping. Are there annotations I can use on NotificationV2 and/or some sort of mapper configuration I can put together in order to hook all of this up?
To flatten your nested classes, you can use the annotation #JsonUnwrapped.
Example:
public class Parent {
public int age;
public Name name;
}
public class Name {
public String first, last;
}
This would normally be serialized as follows:
{
"age" : 18,
"name" : {
"first" : "Joey",
"last" : "Sixpack"
}
}
By updating the parent to use #JsonUnwrapped, we can flatten the nested objects:
public class Parent {
public int age;
#JsonUnwrapped
public Name name;
}
This will output the following:
{
"age" : 18,
"first" : "Joey",
"last" : "Sixpack"
}
See docs for more information

Java controller getting only first item from Set via JSON

Trying to save One to Many JPA relationship. I have written a custom controller. I am getting only the first id in giftSet and not all the ids. I have simplified the code.
My Post request-
{
"name": "Project 7",
"giftSet": [
{
"id": "1"
},
{
"id":"33"
}
]
}
class Holiday{
private String name;
private Set<GiftConfig> giftSets;
}
class GiftSet {
private Integer id;
private Holiday holiday;
}
class GiftConfig {
private Integer id;
private String name;
}
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) {
System.out.println(holiday);
return null;
}
First, I add multiple GiftConfig. After that, while creating Holiday, I add details for GiftSet as well.
In debug mode, I see only id 1 in giftSet and not both ids 1 and 33.
Note- Changing Set to List is not an option.
Introduction
I see 2 problems and one possible last issue.
You are missing setters/getters in order for de-serialization to work on the JSON.
Your payload doesn't seem to be working for me.
As pcoates mentioned in a comment, you could also use #JsonAutoDetect(fieldVisibility = Visibility.ANY) - but I haven't tested this.
Finally, also be careful about having a circular reference if you convert from java back to JSON. I see that a Holiday has a set of giftSets, but a giftSet points to a holiday.
If the gitset points to the same parent holiday, this is a circular reference and will crash.
Getters and Setters
Your problem is that you are missing getters and setters.
Either use lombok and add a #data annotation or add a getter and setter .
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}
Payload
I used the following payload:
{
"name": "HolidaySet",
"giftSets": [
{
"id": 1111,
"holiday": {
"name": null,
"giftSets": null
}
},
{
"id": 1112,
"holiday": {
"name": null,
"giftSets": null
}
}
]
}
Quick Test
I did a quick test to see what the payload should be like.
#RequestMapping(method = RequestMethod.POST, value="/api/saveholiday")
public ResponseEntity<Map<String, Holiday>> saveHoliday(#RequestBody Holiday holiday) throws JsonProcessingException {
System.out.println(holiday);
fakeItTest();
return null;
}
private void fakeItTest() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
Set<GiftSet> giftSets2 = new HashSet<>();
GiftSet gg = new GiftSet();
gg.setId(1111);
gg.setHoliday(new Holiday());
giftSets2.add(gg);
GiftSet gg2 = new GiftSet();
gg2.setId(1112);
gg2.setHoliday(new Holiday());
giftSets2.add(gg2);
Holiday holiday2 = new Holiday();
holiday2.setName("HolidaySet");
holiday2.setGiftSets(giftSets2);
String a = objectMapper.writeValueAsString(holiday2);
System.out.println(a);
}
#Data
public static class Holiday{
private String name;
private Set<GiftSet> giftSets;
}
#Data
public static class GiftSet {
private Integer id;
private Holiday holiday;
}

Java Spring + MongoTemplate cannot retrieve List of Object

I had a Java Class linked to a MongoDB Collection:
#Document(collection = "my_collection")
public class Ev extends MyDTO{
#Id
private String id;
#Indexed
private String sessionId;
private List<String> findings;
}
I had to change findings in this
private List<MyObject> findings;
Declared as
public class MyObject {
private String find;
private String description;
private int number;
private List<SecondaryObj> details;
}
Here are the constructors
public MyObject(String find, int number) {
super();
this.find= find;
this.number= number;
}
public MyObject(String find, int number, List<SecondaryObj> details) {
super();
this.find= find;
this.details = details;
this.number= number;
}
So in mongoDB I have a situation similar to
{
"_id" : ObjectId("5b487a2667a1aa18f*******"),
"sessionId" : "abc123mySessionId",
"findings" : [
{
"find" : "HTTPS",
"description" : "I found HTTPS",
"number" : 10,
"details": [
{"a":"1", "b":"2"},
{"a":"2", "b":"3"}
]
},
{
"find" : "NAME",
"description" : "I found name",
"number" : 3,
"details": [
{"a":"1", "b":"2"},
{"a":"2", "b":"3"}
]
}
]
}
I obviously updated all the methods to match the new data set, but if I try to retrieve
Query searchQuery = new Query(Criteria.where("sessionId").is("abc123mySessionId"));
Ev result = mongoTemplate.findOne(searchQuery, Ev.class);
I obtain this error
Request processing failed; nested exception is org.springframework.data.mapping.model.MappingInstantiationException: Failed to instantiate com.my.project.domain.MyObject using constructor NO_CONSTRUCTOR with arguments
with root cause
java.lang.NoSuchMethodException: om.my.project.domain.MyObject.<init>()
I'm using spring-data-mongodb version 2.0.8 and mongo-java-driver version 3.8.0
I think I should declare MyObject somewhere, but I'm pretty new in Spring, so I'm trying in a kinda blind way... Any suggestion?
You have two non-zero-argument constructors and Spring does not know which one to call. It tries to call no-args constructor, but your class does not have that one.
Check Spring Data Mongo docs
You can create no-args constructor and mark it with #PersistenceConstructor annotation. This way Spring calls it to create an object and sets fields via reflection based on a document fields names, so no setters are required.
#Document(collection = "my_collection")
public class Ev extends MyDTO{
#Id
private String id;
#Indexed
private String sessionId;
private List<MyObject> findings;}
public class MyObject {
private String find;
private String description;
private int number;}
In this it work fine for me in spring-boot-starter-data-mongodb - version 2.0.3.RELEASE

parse JavaBeans sources to json descriptor

I'm looking for a tool or a way to analyze standard JavaBeans source code (with getters and setters) and generate some sort of json descriptors..
maybe with grunt or ant or whatever.
example:
FilterBean.java:
package com.abc.beans;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
public class FilterBean implements Serializable {
private static final long serialVersionUID = 7490361447912259765L;
private Map<String, List<LabelValueBean>> filterMapList;
private String name;
public Map<String, List<LabelValueBean>> getFilterMapList() {
return this.filterMapList;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
LabelValueBean.java:
package com.abc.beans;
import java.io.Serializable;
import java.util.List;
public class LabelValueBean implements Serializable {
private static final long serialVersionUID = 1237198378921379812L;
private String label;
private Integer id;
private List<String> values;
public String getLabel() {
return this.label;
}
public void setLabel(String label) {
this.label = label;
}
public Integer getId() {
return this.idlabel;
}
public List<String> getValues() {
return this.values;
}
public void setValues(List<String> values) {
this.values = values;
}
}
outputs something like:
com.abc.beans.FilterBean.json:
{
"name" : {
"type" : "String",
"setter" : true
},
"filterMapList" : {
"type" : "Map",
"innerType" : "com.abc.beans.LabelValueBean",
"setter" : false
}
}
com.abc.beans.LabelValueBean.json:
{
"label" : {
"type" : "String",
"setter" : true
},
"values" : {
"type" : "Array",
"innerType" : "String",
"setter" : true
},
"id" : {
"type" : "Integer",
"setter" : false
}
}
any idea ?
There's plenty of tools that will take an object-graph and convert them to JSON. Jackson and GSON are two that are commonly used.
But these things only represent data, and may not given you the full picture about the structure. If you want to communicate the structure outside of a Java environment what you may want to do is generate a "JSON Schema". There's another question which discusses how you might do that with a Jackson module.

Order of JSON objects using Jackson's ObjectMapper

I'm using ObjectMapper to do my java-json mapping.
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
ow.writeValue(new File( fileName +".json"), jsonObj);
this is my java class:
public class Relation {
private String id;
private String source;
private String target;
private String label;
private List<RelAttribute> attributes;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getTarget() {
return target;
}
public void setTarget(String target) {
this.target = target;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public void setAttributes(List<RelAttribute> attributes) {
this.attributes = attributes;
}
public List<RelAttribute> getAttributes() {
return attributes;
}
}
this is what I get:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ],
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0"
}
So my question now is, how can I get this json output:
{
"id" : "-75da69d3-79c8-4000-a3d8-b10350a57a7e",
"label" : "new Label",
"target" : "-46b238ac-b8b3-4230-b32c-be9707f8b691",
"source" : "-daa34638-061a-45e0-9f2e-35afd6c271e0",
"attributes" : [ {
"attrName" : "ID",
"attrValue" : ""
}, {
"attrName" : "Description",
"attrValue" : "Primary Actor"
}, {
"attrName" : "Status",
"attrValue" : ""
} ]
}
I want it with same order as in my java declaration. Is there a way to specify it ? Maybe with annotations or stuff like that ?
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
Do you know there is a convenient way to specify alphabetic ordering?
#JsonPropertyOrder(alphabetic = true)
public class Relation { ... }
If you have specific requirements, here how you configure custom ordering:
#JsonPropertyOrder({ "id", "label", "target", "source", "attributes" })
public class Relation { ... }
The ordering of fields within a generated .class is indeterminate, so you can't count on that.
If you want specific ordering per class then you'll need to use the one of the approaches specified in other answers.
If you want everything to default to alphabetical ordering (e.g. for consistency in how the JSON is structured) then you can configure the ObjectMapper like this:
ObjectMapper mapper = new ObjectMapper();
mapper.setConfig(mapper.getSerializationConfig()
.with(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY));
For more consistent JSON consider also adding:
.with(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS)
One advantage of this approach is that you don't have to modify each class being serialized.
I discovered a third way today in case alphabetic is not your desired sorting order. It turns out adding a #JsonProperty annotation on a field places it last when writing. I discovered that when I wanted to specify a property name which did not conform to java naming conventions.
By Adding an index attribute you can define the order. Lowest index is placed first.
#JsonProperty(index=20)
String prop1;
#JsonProperty(index=10)
String prop2;
Would render:
{"prop2": "valueProp2", "prop1": "valueProp1"}
You can use #XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "response", propOrder = { "prop1", "prop2",
"prop3", "prop4", "prop5", "prop6" }).
#JsonPropertyOrder requires a new jar to be added.
As per this documentation, you can configure Jackson2ObjectMapperBuilder globally. This class is available in spring-web dependency.
#Bean
public Jackson2ObjectMapperBuilder objectMapperBuilder() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.featuresToEnable(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY);
return builder;
}
Also, you can use #JsonProperty(index) to determine the order in inherited classes as well.
class animal {
#JsonProperty(index=2)
int p1;
#JsonProperty(index=3)
int p2;
}
class cat extends animal{
#JsonProperty(index=1)
int p3;
}

Categories