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
Related
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);
}
}
There's a few topics like this, however I've read them all and still no luck.
I have a class to which I've made to deserialize some JSON responses from a web service. In short, I've spent too much time looking at this and I'm hoping someone can pick out the error of my ways. As per title, I'm using the Jackson libs.
Snippet of the class below:
final class ContentManagerResponse implements Serializable {
#JsonProperty("Results")
private List<OrgSearchResult> results = null;
#JsonProperty("PropertiesAndFields")
private PropertiesAndFields propertiesAndFields;
#JsonProperty("TotalResults")
private Integer totalResults;
#JsonProperty("CountStringEx")
private String countStringEx;
#JsonProperty("MinimumCount")
private Integer minimumCount;
#JsonProperty("Count")
private Integer count;
#JsonProperty("HasMoreItems")
private Boolean hasMoreItems;
#JsonProperty("SearchTitle")
private String searchTitle;
#JsonProperty("HitHighlightString")
private String hitHighlightString;
#JsonProperty("TrimType")
private String trimType;
#JsonProperty("ResponseStatus")
private ResponseStatus responseStatus;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("Results")
public List<OrgSearchResult> getResults() {
return results;
}
#JsonProperty("Results")
public void setResults(List<OrgSearchResult> results) {
this.results = results;
}
//additional getters and setters.
As said, Results is the property which seems to be having the error.
The JSON response is below.
{
"Results": [
{
"TrimType": "Location",
"Uri": 1684
}
],
"PropertiesAndFields": {},
"TotalResults": 1,
"CountStringEx": "1 Location",
"MinimumCount": 1,
"Count": 0,
"HasMoreItems": false,
"SearchTitle": "Locations - type:Organization and id:24221",
"HitHighlightString": "",
"TrimType": "Location",
"ResponseStatus": {}
}
I'm using the same class to deserialize the following response and it works:
{
"Results": [
{
"LocationIsWithin": {
"Value": true
},
"LocationSortName": {
"Value": "GW_POS_3"
},
"LocationTypeOfLocation": {
"Value": "Position",
"StringValue": "Position"
},
"LocationUserType": {
"Value": "RecordsWorker",
"StringValue": "Records Co-ordinator"
},
"TrimType": "Location",
"Uri": 64092
}
],
"PropertiesAndFields": {},
"TotalResults": 1,
"MinimumCount": 0,
"Count": 0,
"HasMoreItems": false,
"TrimType": "Location",
"ResponseStatus": {}
}
Is the error message just misleading? The structure is identical aside from the second (working) payload not having some of the fields present in the class. I'd expect this one to error if anything.
For what its worth I've also included the OrgSearchResult class below:
final class OrgSearchResult implements Serializable {
#JsonProperty("TrimType") private String trimType;
#JsonProperty("Uri") private String uri;
#JsonIgnore private Map<String, Object> additionalProperties = new HashMap<String, Object>();
//getters and setters
A lot of troubleshooting. I've even tried to use ignore properties can't seem to get them to work.
Full error:
org.codehaus.jackson.map.exc.UnrecognizedPropertyException:
Unrecognized field "Results" (Class
sailpoint.doet.contentmanager.ContentManagerResponse), not marked as
ignorable at [Source: java.io.StringReader#5c6648b0; line: 1, column:
13] (through reference chain:
sailpoint.doet.contentmanager.ContentManagerResponse["Results"])
You can improve readability of POJO class by using PropertyNamingStrategy.UPPER_CAMEL_CASE strategy. Also, you can use JsonAnySetter annotation to read all extra properties. Below example shows how model could look like:
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.setPropertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);
System.out.println(mapper.readValue(jsonFile, ContentManagerResponse.class));
}
}
class ContentManagerResponse {
private List<OrgSearchResult> results;
private Map<String, Object> propertiesAndFields;
private Integer totalResults;
private String countStringEx;
private Integer minimumCount;
private Integer count;
private Boolean hasMoreItems;
private String searchTitle;
private String hitHighlightString;
private String trimType;
private Map<String, Object> responseStatus;
// getters, setters, toString
}
class OrgSearchResult {
private String trimType;
private String uri;
private Map<String, Object> additionalProperties = new HashMap<>();
#JsonAnySetter
public void additionalProperties(String name, Object value) {
additionalProperties.put(name, value);
}
// getters, setters, toString
}
For first JSON payload above code prints:
ContentManagerResponse{results=[OrgSearchResult{trimType='Location', uri='1684', additionalProperties={}}], propertiesAndFields={}, totalResults=1, countStringEx='1 Location', minimumCount=1, count=0, hasMoreItems=false, searchTitle='Locations - type:Organization and id:24221', hitHighlightString='', trimType='Location', responseStatus='{}'}
For second JSON payload above code prints:
ContentManagerResponse{results=[OrgSearchResult{trimType='Location', uri='64092', additionalProperties={LocationSortName={Value=GW_POS_3}, LocationUserType={Value=RecordsWorker, StringValue=Records Co-ordinator}, LocationIsWithin={Value=true}, LocationTypeOfLocation={Value=Position, StringValue=Position}}}], propertiesAndFields={}, totalResults=1, countStringEx='null', minimumCount=0, count=0, hasMoreItems=false, searchTitle='null', hitHighlightString='null', trimType='Location', responseStatus='{}'}
You do not need to implement Serializable interface.
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;
}
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
I'm using Jackson Json. I can't serialize class fields if class extends ArrayList.
Class:
public class DataElement {
private Date date;
private int val1;
private int val2;
// constructor, getters, setters
}
public class DataArray extends ArrayList<DataElement> {
private String info;
private int num;
// constructor, getters, setters
}
Serialization:
ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.writeValue(new File("path"), dataArray);
Result file contains DataElements only:
[ {
"date" : 1446405540000,
"val1" : 10296,
"val2" : 30365
}, {
"date" : 1446405600000,
"val1" : 40164,
"val2" : 20222
} ]
'num' and 'info' are not saved into file.
How to save full class including its fields?
Jackson will serialize your POJOs according to the JsonFormat.Shape. For an ArrayList object that is ARRAY. You can change the shape to OBJECT with an annotation.
#JsonFormat(shape = JsonFormat.Shape.OBJECT)
public class DataArray extends ArrayList<DataElement> {
Make sure DataArray has a getter that returns an ArrayList for e.g.
public ArrayList<DataElement> getContents() {
return new ArrayList<>(this);
}
When I tried the above code I saw this field at the resulting JSON
"empty":false
You can use #JsonIgnore to prevent that from appearing