Jackson serialization duplicate field with #JsonAnyGetter - java

I have my DTO class with predefined fields and map for non-mapped. When otherFields map contains field with same name (field1 key) jackson serialize both of them and I have non-valid json like
{
"field1": "value",
"field1": "otherValue"
}
public class DTO implements Serializable {
private String field1;
#JsonIgnore
#JsonAnySetter
private final Map<String, Object> otherFields = new LinkedHashMap<>();
#JsonAnyGetter
public Map<String, Object> getOtherFields() {
return otherFields;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
}
Is there a way in this case pick one only value? For my case one from map should have priority.

The simplest solution I would propose is to ignore property and in getter marked by JsonAnyGetter annotation add extra logic which would check this condition:
class DTO {
#JsonIgnore
private String field1;
private final Map<String, Object> otherFields = new LinkedHashMap<>();
#JsonAnyGetter
public Map<String, Object> getOtherFields() {
otherFields.putIfAbsent("field1", field1);
return otherFields;
}
#JsonAnySetter
public void setOtherFields(String key, Object value) {
otherFields.put(key, value);
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
}
It is simple, but breaks getter by modifying otherFields Map. We can improve it by adding one classical getter and one for a Jackson:
class DTO {
private String field1;
private final Map<String, Object> otherFields = new LinkedHashMap<>();
#JsonIgnore
public Map<String, Object> getOtherFields() {
return otherFields;
}
#JsonAnyGetter
private Map<String, Object> internalJacksonGetOtherFields() {
otherFields.putIfAbsent("field1", field1);
return otherFields;
}
#JsonAnySetter
public void setOtherFields(String key, Object value) {
otherFields.put(key, value);
}
#JsonIgnore
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
}

Related

Spring #Service logic with Builder Pattern

I have implemented service logic using builder pattern like below in a Spring boot application.
#Service
public class EmailServiceImpl implements NotificationService{
private Map<String, Object> emailTemplateContext;;
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
public EmailServiceImpl() {
}
private EmailServiceImpl(String templateName, String subjectName, List<String> recipients,
List<String> ccRecipients,
Map<String, Object> emailTemplateContext) {
this.emailTemplateContext = emailTemplateContext;
this.recipients = recipients;
this.templateName = templateName;
this.subjectName = subjectName;
this.ccRecipients = ccRecipients;
}
public static class EmailBuilder {
private Map<String, Object> emailTemplateContext = new HashMap<String, Object>();
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
public EmailBuilder withTemplateName(String templateName) {
this.templateName = templateName;
return this;
}
public EmailBuilder withSubjectName(String subjectName) {
this.subjectName = subjectName;
return this;
}
public EmailBuilder withTemplateContextValues(String key, String value) {
this.emailTemplateContext.put(key, value);
return this;
}
public EmailBuilder withTemplateContextValues(String key,List< String> value) {
this.emailTemplateContext.put(key, value);
return this;
}
public EmailBuilder withRecipients(List<String> recipients) {
this.recipients = recipients;
return this;
}
public EmailBuilder withCCRecipients(List<String> ccRecipients) {
this.ccRecipients = ccRecipients;
return this;
}
public EmailServiceImpl build() {
return new EmailServiceImpl(templateName, subjectName, recipients, ccRecipients, emailTemplateContext);
}
}
public void send() {
// implement email send logic
}
}
Then this will be injected in another class something like below
public class EmailSender{
private EmailBuilder emailBuilder;
EmailSender(EmailBuilder emailBuilder){
this.emailBuilder=emailBuilder;
}
public void sendEmail(){
this.emailBuilder
.withRecipients(executionRunBO.getEmailRecipients().stream().map(email -> email.getEmail())
.collect(Collectors.toList()))
.withSubjectName("Reports Draft ").withTemplateName("/emails/reports.ftlh")
.withTemplateContextValues("userName", "TestUser").build().send();
}
}
There are few questions related to the Builder pattern with Spring.
Since #service constructor is private Spring framework can not initiate the bean class.To avoid getting initialization exception i have made constructor to public but it is not the builder pattern.So how do i implement builder pattern correctly with Spring framework?
How do i call the EmailServiceImpl class's EmailBuilder outside from the service class? Since in my examples i have injected EmailBuilder as a constructor argument then Spring framework throws that
required a bean of type '...EmailServiceImpl$EmailBuilder' that could
not be found.
Please help me to clarify those things.
You are mixing a lot of functionality together here. I would make this more streamlined.
I would create a dto class Email, not a Spring bean.
public class Email {
private Map<String, Object> emailTemplateContext;
private List<String> recipients;
private String templateName;
private String subjectName;
private List<String> ccRecipients;
private Email(String templateName, String subjectName, List<String> recipients,
List<String> ccRecipients,
Map<String, Object> emailTemplateContext) {
this.emailTemplateContext = emailTemplateContext;
this.recipients = recipients;
this.templateName = templateName;
this.subjectName = subjectName;
this.ccRecipients = ccRecipients;
}
public static class EmailBuilder {
// your builder code, just build the Email
}
}
And then have a service to send emails.
#Service
public class EmailServiceImpl implements NotificationService {
public void sendEmail(Email email) {
}
}
Call it like this
emailService.sendEmail(new Email.EmailBuilder().build());
There is no need to create a #service from the Email and EmailBuilder.

jackson mapping nested hashmap to nested pojo class in java

I have a nested java map like this
inputMap: {jobId={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502260561923, root/im]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returncode=4096, messageId=null, arguments=null, message=null}
which I want to map to java pojo and here is my pojo classes.
#Getter
#Setter
#ToString
public class DMResponseMapper {
#Getter
#Setter
#ToString
public static class GetSysConfigDMResponseMapper {
#JsonProperty("jobId")
private EndpointReferenceMapper endpointReferenceMapper;
private Integer returnCode;
private String messageId;
private String arguments;
private String message;
#Getter
#Setter
#ToString
public static class EndpointReferenceMapper {
#JsonProperty("ReferenceParameters")
private ReferenceParametersMapper referenceParametersMapper;
#JsonProperty("Address")
private String address;
#Getter
#Setter
#ToString
public static class ReferenceParametersMapper {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSetMapper selectorSetMapper;
#Getter
#Setter
#ToString
public static class SelectorSetMapper {
#JsonProperty("Selector")
private List<String> selector;
}
}
}
}
}
but objectMapper.convertValue(inputMap, GetSysConfigDMResponseMapper.class) is NOT mapping the nested classes.. just the top level fields.
My objectMapper is instantiated like this:
static {
objectMapper = new ObjectMapper();
objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
Response Object is :
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper=DMResponseMapper.GetSysConfigDMResponseMapper.EndpointReferenceMapper(referenceParametersMapper=null, address=null), returnCode=4096, messageId=null, arguments=null, message=null)
Can anyone please suggest, what is wrong here?
Upon debugging this is what I see:
Converted endpointReferenceMapper to type Object.
DMResponseMapper.GetSysConfigDMResponseMapper(endpointReferenceMapper={EndpointReference={ReferenceParameters={ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job, SelectorSet={Selector=[JID_502318722705, root/dcim]}}, Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous}}, returnCode=4096, messageId=null, arguments=null, message=null)
The DMResponseMapper pojo needs to follow the structure of your source data more closely.
Your source Map object has the following structure, based on the info in the question:
inputMap:
{
jobId={
EndpointReference={
ReferenceParameters={
ResourceURI=http://schemas.com/wbem/wscim/1/cim-schema/2/Job,
SelectorSet={
Selector=[JID_502260561923, root/im]
}
},
Address=http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous
}
},
returncode=4096,
messageId=null,
arguments=null,
message=null
}
So, I adapted your DMResponseMapper pojo class to more closely map to that structure - and I changed the nested class names as well. Here is a summary of the nested classes with their fields for your data:
//
// NOT the actual class - just an overview of the structure!
//
class DMResponseMapper {
private JobId jobId;
private Integer returncode;
private Object messageId;
private Object arguments;
private Object message;
class JobId {
private EndpointReference endpointReference;
class EndpointReference {
private ReferenceParameters referenceParameters;
private String address;
class ReferenceParameters {
private String resourceURI;
private SelectorSet selectorSet;
class SelectorSet {
private List<String> selector = null;
}
}
}
}
}
This gave me the following, when fleshed out with annotations and getters/setters:
//
// Here is the actual class, based on the above structure.
//
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
public class DMResponseMapper {
#JsonProperty("jobId")
private JobId jobId;
#JsonProperty("returncode")
private Integer returncode;
#JsonProperty("messageId")
private Object messageId;
#JsonProperty("arguments")
private Object arguments;
#JsonProperty("message")
private Object message;
#JsonProperty("jobId")
public JobId getJobId() {
return jobId;
}
#JsonProperty("jobId")
public void setJobId(JobId jobId) {
this.jobId = jobId;
}
#JsonProperty("returncode")
public Integer getReturncode() {
return returncode;
}
#JsonProperty("returncode")
public void setReturncode(Integer returncode) {
this.returncode = returncode;
}
#JsonProperty("messageId")
public Object getMessageId() {
return messageId;
}
#JsonProperty("messageId")
public void setMessageId(Object messageId) {
this.messageId = messageId;
}
#JsonProperty("arguments")
public Object getArguments() {
return arguments;
}
#JsonProperty("arguments")
public void setArguments(Object arguments) {
this.arguments = arguments;
}
#JsonProperty("message")
public Object getMessage() {
return message;
}
#JsonProperty("message")
public void setMessage(Object message) {
this.message = message;
}
public static class JobId {
#JsonProperty("EndpointReference")
private EndpointReference endpointReference;
#JsonProperty("EndpointReference")
public EndpointReference getEndpointReference() {
return endpointReference;
}
#JsonProperty("EndpointReference")
public void setEndpointReference(EndpointReference endpointReference) {
this.endpointReference = endpointReference;
}
public static class EndpointReference {
#JsonProperty("ReferenceParameters")
private ReferenceParameters referenceParameters;
#JsonProperty("Address")
private String address;
#JsonProperty("ReferenceParameters")
public ReferenceParameters getReferenceParameters() {
return referenceParameters;
}
#JsonProperty("ReferenceParameters")
public void setReferenceParameters(ReferenceParameters referenceParameters) {
this.referenceParameters = referenceParameters;
}
#JsonProperty("Address")
public String getAddress() {
return address;
}
#JsonProperty("Address")
public void setAddress(String address) {
this.address = address;
}
public static class ReferenceParameters {
#JsonProperty("ResourceURI")
private String resourceURI;
#JsonProperty("SelectorSet")
private SelectorSet selectorSet;
#JsonProperty("ResourceURI")
public String getResourceURI() {
return resourceURI;
}
#JsonProperty("ResourceURI")
public void setResourceURI(String resourceURI) {
this.resourceURI = resourceURI;
}
#JsonProperty("SelectorSet")
public SelectorSet getSelectorSet() {
return selectorSet;
}
#JsonProperty("SelectorSet")
public void setSelectorSet(SelectorSet selectorSet) {
this.selectorSet = selectorSet;
}
public static class SelectorSet {
#JsonProperty("Selector")
private List<String> selector = null;
#JsonProperty("Selector")
public List<String> getSelector() {
return selector;
}
#JsonProperty("Selector")
public void setSelector(List<String> selector) {
this.selector = selector;
}
}
}
}
}
}
This is invoked as follows:
First, some test data:
List<String> selector = new ArrayList();
selector.add("JID_502260561923");
selector.add("root/im");
Map<String, Object> selectorSet = new HashMap();
selectorSet.put("Selector", selector);
String resourceURI = "http://schemas.com/wbem/wscim/1/cim-schema/2/Job";
Map<String, Object> referenceParameters = new HashMap();
referenceParameters.put("ResourceURI", resourceURI);
referenceParameters.put("SelectorSet", selectorSet);
String address = "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous";
Map<String, Object> endpointReference = new HashMap();
endpointReference.put("ReferenceParameters", referenceParameters);
endpointReference.put("Address", address);
Map<String, Object> jobId = new HashMap();
jobId.put("EndpointReference", endpointReference);
Map<String, Object> inputMap = new HashMap();
inputMap.put("jobId", jobId);
inputMap.put("returncode", 4096);
inputMap.put("messageId", "foo");
inputMap.put("arguments", "bar");
inputMap.put("message", "baz");
Note I replaced your null values with strings, for testing and demonstration.
Then the code to perform the mapping:
ObjectMapper objectMapper = new ObjectMapper();
DMResponseMapper mapper = objectMapper.convertValue(inputMap, DMResponseMapper.class);
The resulting mapper object contains the test data:

Converting JSON to XML XmlMapper- Jackson 2.10

I have pojos with #JsonProperties. I use these to read JSON and parse to POJO. I am now having to post these pojos formatted as XML.
The Required XML format to successfully post looks like this (note the namespace type, xsi type formatting):
<network_objects>
<network_object xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="subnetNetworkObjectDTO">
<name>TestSubnet</name>
<display_name>TestSubnet</display_name>
<global>false</global>
<application_id>3</application_id>
<type>subnet</type>
<ip>5.207.206.0</ip>
<netmask>255.255.254.0</netmask>
</network_object>
<network_object xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="networkObjectGroupDTO">
<name>Test01Subnets</name>
<display_name>Test01Subnets</display_name>
<application_id>3</application_id>
<type>group</type>
</network_object>
</network_objects>
I pass the created Application Class (shown below) to convert to XML
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
byte[] val = mapper.writeValueAsBytes(myApp);
The output is a bit off and does not contain the xmlns and the xsi looks different. It also has 'Application' as root:
<Application>
<network_objects>
<network_object>
<network_object>
<#xsi.type>networkObjectGroupDTO</#xsi.type>
<name>name</name>
<display_name>displayName</display_name>
<application_id>3</application_id>
<type>group</type>
</network_object>
</network_object>
</network_objects>
</Application>
When I output the class to JSON, it looks as expected (No "Application" as root).
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
byte[] val = mapper.writeValueAsBytes(myApp);
{
"network_objects" : {
"network_object" : [ {
"#xsi.type" : "networkObjectGroupDTO",
"name" : "name",
"display_name" : "displayName",
"application_id" : 3,
"type" : "group"
}
}
}
What do I need to modify with my XmlMapper() or POJOs in order to get the XML formatted correctly?
Below are the POJOs used for this.
Application Class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"network_objects"
})
public class Application {
#JsonProperty("network_objects")
private NetworkObjects networkObjects;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("network_objects")
public NetworkObjects getNetworkObjects() {
return networkObjects;
}
#JsonProperty("network_objects")
public void setNetworkObjects(NetworkObjects networkObjects) {
this.networkObjects = networkObjects;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
NetworkObjects Class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"network_object"
})
public class NetworkObjects {
#JsonProperty("network_object")
private List<NetworkObject> networkObject = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("network_object")
public List<NetworkObject> getNetworkObject() {
return networkObject;
}
#JsonProperty("network_object")
public void setNetworkObject(List<NetworkObject> networkObject) {
this.networkObject = networkObject;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
NetworkObject Class:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"#xsi.type",
"id",
"uid",
"name",
"display_name",
"global",
"comment",
"application_id",
"type",
"ip",
"access_allowed",
"member",
"last_ip",
"first_ip",
"netmask"
})
public class NetworkObject {
#JsonProperty("#xsi.type")
private String xsiType;
#JsonProperty("id")
private Integer id;
#JsonProperty("uid")
private String uid;
#JsonProperty("name")
private String name;
#JsonProperty("display_name")
private String displayName;
#JsonProperty("global")
private Boolean global;
#JsonProperty("comment")
private String comment;
#JsonProperty("application_id")
private Integer applicationId;
#JsonProperty("type")
private String type;
#JsonProperty("ip")
private String ip;
#JsonProperty("access_allowed")
private Boolean accessAllowed;
#JsonProperty("member")
private List<Member> member = null;
#JsonProperty("last_ip")
private String lastIp;
#JsonProperty("first_ip")
private String firstIp;
#JsonProperty("netmask")
private String netmask;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("#xsi.type")
public String getXsiType() {
return xsiType;
}
#JsonProperty("#xsi.type")
public void setXsiType(String xsiType) {
this.xsiType = xsiType;
}
#JsonProperty("id")
public Integer getId() {
return id;
}
#JsonProperty("id")
public void setId(Integer id) {
this.id = id;
}
#JsonProperty("uid")
public String getUid() {
return uid;
}
#JsonProperty("uid")
public void setUid(String uid) {
this.uid = uid;
}
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
#JsonProperty("display_name")
public String getDisplayName() {
return displayName;
}
#JsonProperty("display_name")
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
#JsonProperty("global")
public Boolean getGlobal() {
return global;
}
#JsonProperty("global")
public void setGlobal(Boolean global) {
this.global = global;
}
#JsonProperty("comment")
public String getComment() {
return comment;
}
#JsonProperty("comment")
public void setComment(String comment) {
this.comment = comment;
}
#JsonProperty("application_id")
public Integer getApplicationId() {
return applicationId;
}
#JsonProperty("application_id")
public void setApplicationId(Integer applicationId) {
this.applicationId = applicationId;
}
#JsonProperty("type")
public String getType() {
return type;
}
#JsonProperty("type")
public void setType(String type) {
this.type = type;
}
#JsonProperty("ip")
public String getIp() {
return ip;
}
#JsonProperty("ip")
public void setIp(String ip) {
this.ip = ip;
}
#JsonProperty("access_allowed")
public Boolean getAccessAllowed() {
return accessAllowed;
}
#JsonProperty("access_allowed")
public void setAccessAllowed(Boolean accessAllowed) {
this.accessAllowed = accessAllowed;
}
#JsonProperty("member")
public List<Member> getMember() {
return member;
}
#JsonProperty("member")
public void setMember(List<Member> member) {
this.member = member;
}
#JsonProperty("last_ip")
public String getLastIp() {
return lastIp;
}
#JsonProperty("last_ip")
public void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
#JsonProperty("first_ip")
public String getFirstIp() {
return firstIp;
}
#JsonProperty("first_ip")
public void setFirstIp(String firstIp) {
this.firstIp = firstIp;
}
#JsonProperty("netmask")
public String getNetmask() {
return netmask;
}
#JsonProperty("netmask")
public void setNetmask(String netmask) {
this.netmask = netmask;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
I used your bean classes and json string. Almost everything is possible with the conversion, but there are some issues. I got this so far, if someone can derive the final solution it will be great.
Application.java
Not really any changes.
NetworkObject.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({"#xsi.type", "id", "uid", "name", "display_name", "global", "comment", "application_id", "type", "ip", "access_allowed", "member", "last_ip", "first_ip", "netmask"})
public class NetworkObject {
#JsonProperty("id")
private Integer id;
#JsonProperty("uid")
private String uid;
#JsonProperty("name")
private String name;
#JsonProperty("display_name")
private String displayName;
#JsonProperty("global")
private Boolean global;
#JsonProperty("comment")
private String comment;
#JsonProperty("application_id")
private Integer applicationId;
#JsonProperty("type")
private String type;
#JsonProperty("#xsi.type")
// CHANGE: You cannot have same element name and attribute name, so I had to change this to xtype if someone knows how to tackle this, that will be final answer
#JacksonXmlProperty(localName = "xtype", isAttribute = true, namespace = "http://www.w3.org/2001/XMLSchema-instance")
private String xsiType;
#JsonProperty("ip")
private String ip;
#JsonProperty("access_allowed")
private Boolean accessAllowed;
#JsonProperty("member")
private List<Member> member = null;
#JsonProperty("last_ip")
private String lastIp;
#JsonProperty("first_ip")
private String firstIp;
#JsonProperty("netmask")
private String netmask;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
// CHANGE: We don't really need on getter setters #JsonProperty("#xsi.type")
public String getXsiType() {
return xsiType;
}
// CHANGE: We don't really need on getter setters #JsonProperty("#xsi.type")
public void setXsiType(String xsiType) {
this.xsiType = xsiType;
}
#JsonProperty("id")
public Integer getId() {
return id;
}
#JsonProperty("id")
public void setId(Integer id) {
this.id = id;
}
#JsonProperty("uid")
public String getUid() {
return uid;
}
#JsonProperty("uid")
public void setUid(String uid) {
this.uid = uid;
}
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
#JsonProperty("display_name")
public String getDisplayName() {
return displayName;
}
#JsonProperty("display_name")
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
#JsonProperty("global")
public Boolean getGlobal() {
return global;
}
#JsonProperty("global")
public void setGlobal(Boolean global) {
this.global = global;
}
#JsonProperty("comment")
public String getComment() {
return comment;
}
#JsonProperty("comment")
public void setComment(String comment) {
this.comment = comment;
}
#JsonProperty("application_id")
public Integer getApplicationId() {
return applicationId;
}
#JsonProperty("application_id")
public void setApplicationId(Integer applicationId) {
this.applicationId = applicationId;
}
#JsonProperty("type")
public String getType() {
return type;
}
#JsonProperty("type")
public void setType(String type) {
this.type = type;
}
#JsonProperty("ip")
public String getIp() {
return ip;
}
#JsonProperty("ip")
public void setIp(String ip) {
this.ip = ip;
}
#JsonProperty("access_allowed")
public Boolean getAccessAllowed() {
return accessAllowed;
}
#JsonProperty("access_allowed")
public void setAccessAllowed(Boolean accessAllowed) {
this.accessAllowed = accessAllowed;
}
#JsonProperty("member")
public List<Member> getMember() {
return member;
}
#JsonProperty("member")
public void setMember(List<Member> member) {
this.member = member;
}
#JsonProperty("last_ip")
public String getLastIp() {
return lastIp;
}
#JsonProperty("last_ip")
public void setLastIp(String lastIp) {
this.lastIp = lastIp;
}
#JsonProperty("first_ip")
public String getFirstIp() {
return firstIp;
}
#JsonProperty("first_ip")
public void setFirstIp(String firstIp) {
this.firstIp = firstIp;
}
#JsonProperty("netmask")
public String getNetmask() {
return netmask;
}
#JsonProperty("netmask")
public void setNetmask(String netmask) {
this.netmask = netmask;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
NetworkObjects
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({"network_object"})
// CHANGE: To provide root element name
#JacksonXmlRootElement(localName = "network_objects")
public class NetworkObjects {
#JsonProperty("network_object")
#JacksonXmlElementWrapper(useWrapping = false)
// CHANGE: To ignore <network_object><network_object></network_object></network_object>
private List<NetworkObject> networkObject = null;
#JsonIgnore
private Map<String, Object> additionalProperties = new HashMap<String, Object>();
#JsonProperty("network_object")
public List<NetworkObject> getNetworkObject() {
return networkObject;
}
#JsonProperty("network_object")
public void setNetworkObject(List<NetworkObject> networkObject) {
this.networkObject = networkObject;
}
#JsonAnyGetter
public Map<String, Object> getAdditionalProperties() {
return this.additionalProperties;
}
#JsonAnySetter
public void setAdditionalProperty(String name, Object value) {
this.additionalProperties.put(name, value);
}
}
Main.java
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper jsonOM = new ObjectMapper();
String jsomn = Files.readAllLines(Paths.get("sample.json"), StandardCharsets.US_ASCII).stream().collect(Collectors.joining(""));
Application myApp = jsonOM.readValue(jsomn, Application.class);
ObjectMapper mapper = new XmlMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
// Not writing entire object
System.out.println(mapper.writer().writeValueAsString(myApp.getNetworkObjects()));
}
OUPUT
<network_objects>
<network_object xmlns:wstxns1="http://www.w3.org/2001/XMLSchema-instance" wstxns1:xtype="networkObjectGroupDTO">
<name>name</name>
<display_name>displayName</display_name>
<application_id>3</application_id>
<type>group</type>
</network_object>
</network_objects>
You can use this:
XMLInputFactory inputFactory = XMLInputFactory.newFactory();
inputFactory.setProperty("javax.xml.stream.isNamespaceAware", false);
XmlMapper xmlMapper = new XmlMapper(inputFactory);

Jersey rest service not returning XMLresponse

I am building a rest API using Jersey where XML and JSON outputs are allowed depending on what format client prefers(using Accept header).The service sends the below class as an output which looks like this
#XmlRootElement
public class ProjectDetails{
private List<Attachment> attachments;
private Map<String, List<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
public Map<String, List<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(BaseUtil.hasText(key)){
List<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, List<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
I don't want attachments field as an output so marked it with #XmlTransient rather I want to form a Map using the attachments field and send it as an output.
In case of JSON format, I am getting the correct response.But in case of XML, I am not getting any output when I hit the service.
I think it is related to this Map field because if I remove Map field and add some other field like String then I get that field in response.
Please let me know how to resolve this.
Update:
After some googling, i found XmlAdapter solution and implemented as below
public class MapAdapter extends
XmlAdapter<MapAdapter.AdaptedMap, Map<String, List<Attachment>>> {
public static class AdaptedEntry {
public String key;
public List<Attachment> value = new ArrayList<Attachment>();
}
public static class AdaptedMap {
List<AdaptedEntry> entries = new ArrayList<AdaptedEntry>();
}
#Override
public AdaptedMap marshal(Map<String, List<Attachment>> map)
throws Exception {
AdaptedMap adaptedMap = new AdaptedMap();
for (Entry<String, List<Attachment>> entry : map.entrySet()) {
AdaptedEntry adaptedEntry = new AdaptedEntry();
adaptedEntry.key = entry.getKey();
adaptedEntry.value = entry.getValue();
adaptedMap.entries.add(adaptedEntry);
}
return adaptedMap;
}
#Override
public Map<String, List<Attachment>> unmarshal(AdaptedMap adaptedMap)
throws Exception {
List<AdaptedEntry> adapatedEntries = adaptedMap.entries;
Map<String, List<Attachment>> map = new HashMap<String, List<Attachment>>(
adapatedEntries.size());
for (AdaptedEntry adaptedEntry : adapatedEntries) {
map.put(adaptedEntry.key, adaptedEntry.value);
}
return map;
}
And then
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, String> getImageCategory() {
But still it's not working..Anything I missed?
I have used your ProjectDetails class a little bit changes I've made, and it provides response for both XML and JSON. Can you try this?
#XmlRootElement
public class ProjectDetails {
private List<Attachment> attachments;
private Map<String, ArrayList<Attachment>> imageCategory;
#XmlTransient
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> attachments) {
this.attachments = attachments;
}
#XmlJavaTypeAdapter(MapAdapter.class)
public Map<String, ArrayList<Attachment>> getImageCategory() {
if(attachments == null || attachments.size() == 0){
return null;
}
Map<String, ArrayList<Attachment>> map = new HashMap<String, ArrayList<Attachment>>();
for (Attachment img : attachments){
String key = img.getCategory();
if(!key.equals("")){
ArrayList<Attachment> values = map.get(key);
if (values == null){
values = new ArrayList<Attachment>();
}
values.add(img);
map.put(key, values);
}
}
this.imageCategory = map ;
return imageCategory;
}
public void setImageCategory(Map<String, ArrayList<Attachment>> imageCategory) {
this.imageCategory = imageCategory;
}
}
And the Adapter class you can use the following
public class MapAdapter extends XmlAdapter<MapElement[], Map<String, ArrayList<Attachment>>>{
public MapElement[] marshal(Map<String, ArrayList<Attachment>> arg0) throws Exception {
MapElement[] mapElements = new MapElement[arg0.size()];
int i = 0;
for (Map.Entry<String, ArrayList<Attachment>> entry : arg0.entrySet()){
mapElements[i++] = new MapElement(entry.getKey(), entry.getValue());
}
return mapElements;
}
public Map<String, ArrayList<Attachment>> unmarshal(MapElement[] arg0) throws Exception {
Map<String, ArrayList<Attachment>> r = new HashMap<String, ArrayList<Attachment>>();
for (MapElement mapelement : arg0)
r.put(mapelement.key, mapelement.value);
return r;
}
}
I've changed the MapElement also
public class MapElement {
#XmlElement
public String key;
#XmlElement
public ArrayList<Attachment> value;
private MapElement() {
}
public MapElement(String key, ArrayList<Attachment> value) {
this.key = key;
this.value = value;
}
}
And the Attachement class should have getter setter methods
public class Attachment {
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
private String category;
public Attachment(String cat){
this.category = cat;
}
}

Spring doesn't save object to MongoDB correctly

Following question has been separated from this one:
ArrayIndexOutOfBoundsException while Spring save data to MongoDB
I have problem with saving Object to MongoDB. I've noticed that problem might be caused by too complex object. I have following class hierarchy:
ClassA is superclass for ClassB and ClassC. ClassD contains map of maps. ClassC contains ClassB.
Code which I invoke is following:
ClassC c = new ClassC()
c.setName("NAME");
mongoOperation.save(c, "Mongo"); // MongoOperations object
The problem is that Mongo doesn't save object's data. It saves only _id and _class.
Actual data
{
"_id" : ObjectId("53e86cd9c506f66eafaa03cb"),
"_class" : "com.sample.ClassC"
}
Expected data
{
"_id" : ObjectId("53e86cd9c506f66eafaa03cb"),
"_class" : "com.sample.ClassC",
"name" : "NAME"
}
Funny thing is that when I comment out map field in ClassD everything works fine.
Is it possible to be caused by too complex object which I try to serialize?
EDIT
When I remove bObject from ClassC it also works fine.
EDIT 2
All classes are simple beans with setters and getters.
e.g.
public class ClassD{
private TreeMap<String, TreeMap<String,String>> map;
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
EDIT 3
Full example below, it has same class hierarchy as picture above.
public class Application implements CommandLineRunner {
#Autowired
private MongoTemplate mongoTemplate;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
ClassC cObject = new ClassC();
cObject.setName("Jon");
try {
mongoTemplate.save(cObject);
}catch(Exception e){
e.printStackTrace();
}
mongoTemplate.save(cObject);
}
}
class ClassA{
private String name;
private ClassD dObject;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ClassD getdObject() {
return dObject;
}
public void setdObject(ClassD dObject) {
this.dObject = dObject;
}
}
class ClassB extends ClassA {
}
class ClassC extends ClassA {
private ClassB b;
public ClassB getB() {
return b;
}
public void setB(ClassB b) {
this.b = b;
}
}
class ClassD {
private TreeMap<String, TreeMap<String, String>> map = new TreeMap<>();
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
I guess the MongoConverter in specific version of your spring-data-mongodb.jar works incorrectly.
Spring must convert your ClassC instance into DBObject format, then call DBCollection.save to save data into database. You can check the content of DBObject parameter in method "com.mongodb.DBCollection.save" whether it contains correct data as you expect.
I copy your ClassC with complete structure and test, it's fine and cannot reproduce what you described above. I use spring-data-mongdb-1.2.3-RELEASE.jar. What's the version you adopt?
The following code seems to work:
#EnableAutoConfiguration
public class Application implements CommandLineRunner {
#Autowired
private MongoTemplate mongoTemplate;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
Customer customer = new Customer("myself");
ClassB classB = new ClassB();
TreeMap<String, TreeMap<String, String>> map = new TreeMap<String, TreeMap<String, String>>();
TreeMap<String, String> innermap = new TreeMap<String, String>();
innermap.put("iam", "cool");
map.put("innermap", innermap);
TreeMap<String, String> innermap2 = new TreeMap<String, String>();
innermap2.put("youare", "yellow");
map.put("innermap2", innermap2);
classB.setMap(map);
customer.setClassB(classB);
try {
mongoTemplate.save(customer);
} catch (Exception e) {
e.printStackTrace();
}
mongoTemplate.save(customer);
System.out.println(mongoTemplate.findAll(Customer.class));;
}
}
public class ClassB {
private TreeMap<String, TreeMap<String, String>> map = new TreeMap<String, TreeMap<String, String>>();
public TreeMap<String, TreeMap<String, String>> getMap() {
return map;
}
public void setMap(TreeMap<String, TreeMap<String, String>> map) {
this.map = map;
}
}
#Document(collection ="customer")
public class Customer {
#Id
private String id;
private String name;
private ClassB classB;
public Customer() {
}
public Customer(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ClassB getClassB() {
return classB;
}
public void setClassB(ClassB classB) {
this.classB = classB;
}
#Override
public String toString() {
return "Customer [id=" + id + ", name=" + name + ", classB=" + classB
+ "]";
}
}
But the ArrayIndexOutOfBoundsException-issue is still present.

Categories

Resources