Polymorphic deserialization in jackson sets value null for specific fields - java

Problem statement
Is that the JSON which is to be deserialize into the below given POJO's
is setting value of credentialType as null when i send the below JSON through postman
{
"credential": [
{
"#type": "mobile",
"credentialName": "cred-2",
"email": "s#s.com"
},
{
"#type": "card",
"credentialNumber": "1"
}
]
}
Expected outcome
What i want to achieve is that with the above JSON the credential type should be set as either MOBILE for MobileCredentialDto or CARD for CardCredentialDto
#Getter
public class SecureDto {
private List<CredentialDto> credential;
#JsonCreator
public HandoutDto(#JsonProperty("credential") final List<CredentialDto> credential) {
this.credential = Collections.unmodifiableList(credential);
}
}
#Getter
public class SecureDto {
private List<CredentialDto> credential;
#JsonCreator
public HandoutDto(#JsonProperty("credential") final List<CredentialDto> credential) {
this.credential = Collections.unmodifiableList(credential);
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
#JsonSubTypes({
#JsonSubTypes.Type(value = CardCredentialDto.class, name = "card"),
#JsonSubTypes.Type(value = MobileCredentialDto.class, name = "mobile"),
})
#Getter
#Setter
public class CredentialDto {
private CredentialType credentialType;
#JsonCreator
public CredentialDto(#JsonProperty("credentialType") final String credentialType) {
this.credentialType = CredentialType.valueOf(credentialType);
}
public CredentialDto() {
}
public void setCredentialType(final CredentialType credentialType) {
this.credentialType = CredentialType.MOBILE;
}
}
#Getter
#Setter
public class MobileCredentialDto extends CredentialDto {
private String credentialName;
private String email;
public MobileCredentialDto(final String credentialId,
final String state,
final String credentialNumber,
final String credentialName,
final String email) {
super(credentialId, state, credentialNumber, CredentialType.MOBILE.name());
this.credentialName = credentialName;
this.email = email;
}
public MobileCredentialDto() {
}
public String getCredentialName() {
return credentialName;
}
public String getEmail() {
return email;
}
}
#Getter
#Setter
public class CardCredentialDto extends CredentialDto {
public CardCredentialDto(final String credentialId,
final String state,
final String credentialNumber) {
super(credentialId, state, credentialNumber,CredentialType.CARD.name());
}
public CardCredentialDto() {
}
}
public enum CredentialType {
MOBILE("MOBILE"),
CARD("CARD");
private final String name;
CredentialType(final String name) {
this.name = name;
}
}

I Found the answer.
what i did was set the visible = true in JsonTypeInfo.
In short by setting the visible = true allowed the jackson to do the following
Note on visibility of type identifier: by default, deserialization (use during reading of JSON) of type identifier is completely handled by Jackson, and is not passed to deserializers. However, if so desired, it is possible to define property visible = true in which case property will be passed as-is to deserializers (and set via setter or field) on deserialization.
Refer the doc here for more understanding.
https://fasterxml.github.io/jackson-annotations/javadoc/2.4/com/fasterxml/jackson/annotation/JsonTypeInfo.html
Below is the code
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME ,
visible = true,
property = "credentialType")
#JsonSubTypes({
#JsonSubTypes.Type(value = CardCredentialDto.class, name = "card"),
#JsonSubTypes.Type(value = MobileCredentialDto.class, name = "mobile"),
})
#Getter
#Setter
public class CredentialDto {
#JsonProperty(value = "credentialType")
private CredentialType credentialType;
#JsonCreator
public CredentialDto(#JsonProperty("credentialType") final String credentialType) {
this.credentialType = CredentialType.valueOf(credentialType);
}
public CredentialDto() {
}
public void setCredentialType(final CredentialType credentialType) {
this.credentialType = CredentialType.MOBILE;
}
}
and the json will look like this
{
"credential": [
{
"credentialType": "mobile",
"credentialName": "cred-2",
"email": "s#s.com"
},
{
"credentialType": "card",
"credentialNumber": "1"
}
]
}

Related

Creating complex JSON payload from Java Pojo Jackson

I want to create below JSON payload
{
"maxResults":3,
"counter":0,
"customerParameters":{
"filters":[
{
"name":"customerId",
"operator":"=",
"value":["hello"]
}
]
},
"dealerParameters":[
{
"name":"club"
},
{
"name":"token"
}
]
}
Coded so far:
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"maxResults",
"counter",
"customerParameters",
"dealerParameters"
})
public class CustomerModel {
#JsonProperty("maxResults")
private Integer maxResults;
#JsonProperty("counter")
private Integer counter;
#JsonProperty("customerParameters")
private CustomerParameters customerParameters;
#JsonProperty("dealerParameters")
private List<DealerParameter> dealerParameters = null;
#JsonProperty("customerParameters")
public CustomerParameters getCustomerParameters() {
return customerParameters;
}
#JsonProperty("customerParameters")
public void setCustomerParameters(CustomerParameters customerParameters) {
this.customerParameters = customerParameters;
}
#JsonProperty("dealerParameters")
public List<DealerParameter> getDealerParameters() {
return dealerParameters;
}
#JsonProperty("dealerParameters")
public void setDealerParameters(List<DealerParameter> dealerParameters) {
this.dealerParameters = dealerParameters;
}
// Getter/Setter for other params
}
CustomerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"filters"
})
public class CustomerParameters {
#JsonProperty("filters")
private List<Filter> filters = null;
// Setter and Getter for filters parameter
}
DealerParameters.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name"
})
public class DealerParameter {
#JsonProperty("name")
private String name;
#JsonProperty("name")
public String getName() {
return name;
}
#JsonProperty("name")
public void setName(String name) {
this.name = name;
}
}
Filter.java
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonPropertyOrder({
"name",
"operator",
"value"
})
public class Filter {
#JsonProperty("name")
private String name;
#JsonProperty("operator")
private String operator;
#JsonProperty("value")
private List<String> value = null;
#JsonProperty("value")
public List<String> getValue() {
return value;
}
#JsonProperty("value")
public void setValue(List<String> value) {
this.value = value;
}
// Setter and Getter for other properties
}
Missing Part:
#Controller
public class TestContoller {
RestTemplate restTemplate;
Should I instantiate each pojo class with new operator as below and set all required parameters ? or any other approach of creating JSON payload?
CustomerModel customerModel= new CustomerModel();
customerModel.setMaxResults(1);
Filter filter= new Filter();
filter.setName("customerID");
filter.setOperator("-");
filter.setValue(Arrays.asList("club"));
CustomerParameters customerParameters = new CustomerParameters();
customerParameters.setFilters(Arrays.asList(filter));
customerModel.setCustomerParameters(customerParameters);
For DealerParameter class, I want to create multiple objects with same key different value(see the json payload I mentioned above). Below code creates only one object "name":"dealerId"
DealerParameter dealerParameter = new DealerParameter();
dealerParameter.setName("dealerId");
customerModel.setDealerParameters(dealerParameter);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writeValueAsString(customerModel);
restTemplate.exchange(todo); // restful service call
}
you are already using "ObjectMapper", And ObjectMapper has readValue() method. By using readValue() method you can populate all data at a time like below:--
ObjectMapper objectMapper = new ObjectMapper();
//populating data from json string to POJO
CustomerModel customerModel = objectMapper.readValue(<json String>,CustomerModel.class);
System.out.println(objectMapper.writeValueAsString(customerModel); // print all data

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));
}
}

Simple xml returns null value for attribute field

I want to use Simple XML to deserialize the following XML into a POJO:
<shippingInfo>
<shippingServiceCost currencyId="USD">9.8</shippingServiceCost>
<shippingType>Flat</shippingType>
<shipToLocations>Worldwide</shipToLocations>
<expeditedShipping>true</expeditedShipping>
<oneDayShippingAvailable>false</oneDayShippingAvailable>
<handlingTime>3</handlingTime>
</shippingInfo>
I have created the following class to do so. However, I'm having trouble in that the currencyId attribute isn't being properly deserialized.
#Root(name = "shippingInfo")
public class ShippingInfo {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
#Attribute(name = "currencyId", required = false)
private String currencyId;
#Element(name = "shippingType", required = false)
private String shippingType;
#Element(name = "shipToLocations" ,required = false)
private String shipToLocations;
#Element(name = "expeditedShipping", required = false)
private Boolean expeditedShipping;
#Element(name = "oneDayShippingAvailable", required = false)
private Boolean oneDayShippingAvailable;
#Element(name = "handlingTime", required = false)
private Integer handlingTime;
// Getters & Setters
public BigDecimal getShippingServiceCost() {
return shippingServiceCost;
}
public void setShippingServiceCost(BigDecimal shippingServiceCost) {
this.shippingServiceCost = shippingServiceCost;
}
public String getCurrencyId() {
return currencyId;
}
public void setCurrencyId(String currencyId) {
this.currencyId = currencyId;
}
public String getShippingType() {
return shippingType;
}
public void setShippingType(String shippingType) {
this.shippingType = shippingType;
}
public String getShipToLocations() {
return shipToLocations;
}
public void setShipToLocations(String shipToLocations) {
this.shipToLocations = shipToLocations;
}
public Boolean isExpeditedShipping() {
return expeditedShipping;
}
public void setExpeditedShipping(Boolean bool) {
this.expeditedShipping = bool;
}
public Boolean isOneDayShippingAvailable() {
return oneDayShippingAvailable;
}
public void setOneDayShippingAvailable(Boolean bool) {
this.oneDayShippingAvailable = bool;
}
public Integer getHandlingTime() {
return handlingTime;
}
public void setHandlingTime(Integer days) {
this.handlingTime = days;
}
}
I would expect the value of currencyId to be "USD" after deserializing, but I'm getting null. All the element values appear to deserialize properly. Does anyone have a suggestion on how to fix this?
Moreover, in a case such as the following instance:
<sellingStatus>
<currentPrice currencyId="USD">125.0</currentPrice>
<convertedCurrentPrice currencyId="USD">125.0</convertedCurrentPrice>
<bidCount>2</bidCount>
<sellingState>EndedWithSales</sellingState>
</sellingStatus>
Where there are two attributes named currencyId on two distinct elements, how can I go about deserializing these into separate fields? I have created a similar SellingStatus class but am unsure how to distinguish between the currencyId attributes.
Thank you!
Edit: Per suggestions I tried adding a custom ShippingServiceCost class to ShippingInfo as follows:
#Element(name = "shippingServiceCost", required = false)
private ShippingServiceCost shippingServiceCost;
Which in turn looks like this:
public class ShippingServiceCost {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
#Attribute(name = "currencyId", required = false)
private String currencyId;
// getters and setters
}
But when I try to access both the shippingServiceCost field and the currencyId field, I get null in every instance (even though I know there is data). Any suggestions are greatly appreciated.
For the above code, SimpleXML expects the currencyId to be present as <shippingInfo currencyId="USD">.
So to solve it, you need to create another class called ShippingServiceCost which will contain the currencyId attribute and the BigDecimal
This will also solve your second query. You can do it by creating two classes CurrentPrice and ConvertedCurrentPrice which will contain the currencyId attribute.
The only working solution is creating a Converter class, see code below:
public class ShippingInfoConverter implements Converter<ShippingInfo> {
#Override
public ShippingInfo read(InputNode inputNode) throws Exception {
ShippingInfo shippingInfo = new ShippingInfo();
InputNode shippingServiceCostNode = inputNode.getNext("shippingServiceCost");
shippingInfo.setShippingServiceCost(new BigDecimal(shippingServiceCostNode.getValue()));
shippingInfo.setCurrencyId(shippingServiceCostNode.getAttribute("currencyId").getValue());
shippingInfo.setShippingType(inputNode.getNext("shippingType").getValue());
shippingInfo.setShipToLocations(inputNode.getNext("shipToLocations").getValue());
shippingInfo.setExpeditedShipping(Boolean.parseBoolean(inputNode.getNext("expeditedShipping").getValue()));
shippingInfo.setOneDayShippingAvailable(Boolean.parseBoolean(inputNode.getNext("oneDayShippingAvailable").getValue()));
shippingInfo.setHandlingTime(Integer.valueOf(inputNode.getNext("handlingTime").getValue()));
return shippingInfo;
}
#Override
public void write(OutputNode outputNode, ShippingInfo shippingInfo) throws Exception {
OutputNode shippingServiceCostNode = outputNode.getChild("shippingServiceCost");
shippingServiceCostNode.setValue(shippingInfo.getShippingServiceCost().toString());
shippingServiceCostNode.setAttribute("currencyId", shippingInfo.getCurrencyId());
outputNode.getChild("shippingType").setValue(shippingInfo.getShippingType());
outputNode.getChild("shipToLocations").setValue(shippingInfo.getShipToLocations());
outputNode.getChild("expeditedShipping").setValue(Boolean.toString(shippingInfo.isExpeditedShipping()));
outputNode.getChild("oneDayShippingAvailable").setValue(Boolean.toString(shippingInfo.isOneDayShippingAvailable()));
outputNode.getChild("handlingTime").setValue(Integer.toString(shippingInfo.getHandlingTime()));
}
}
Note how 'currencyId' is set, using the node's getAttribute method.
shippingInfo.setCurrencyId(shippingServiceCostNode.getAttribute("currencyId").getValue());
Also note how the element 'shippingServiceCost' gets the attribute
shippingServiceCostNode.setAttribute("currencyId", shippingInfo.getCurrencyId());
A few other things are need to get this working, starting with your POJO
#Root(name = "shippingInfo")
#Convert(ShippingInfoConverter.class)
public class ShippingInfo {
#Element(name = "shippingServiceCost", required = false)
private BigDecimal shippingServiceCost;
private String currencyId;
#Element(name = "shippingType", required = false)
private String shippingType;
#Element(name = "shipToLocations" ,required = false)
private String shipToLocations;
#Element(name = "expeditedShipping", required = false)
private Boolean expeditedShipping;
#Element(name = "oneDayShippingAvailable", required = false)
private Boolean oneDayShippingAvailable;
#Element(name = "handlingTime", required = false)
private Integer handlingTime;
// Getters & Setters
public BigDecimal getShippingServiceCost() {
return shippingServiceCost;
}
public void setShippingServiceCost(BigDecimal shippingServiceCost) {
this.shippingServiceCost = shippingServiceCost;
}
public String getCurrencyId() {
return currencyId;
}
public void setCurrencyId(String currencyId) {
this.currencyId = currencyId;
}
public String getShippingType() {
return shippingType;
}
public void setShippingType(String shippingType) {
this.shippingType = shippingType;
}
public String getShipToLocations() {
return shipToLocations;
}
public void setShipToLocations(String shipToLocations) {
this.shipToLocations = shipToLocations;
}
public Boolean isExpeditedShipping() {
return expeditedShipping;
}
public void setExpeditedShipping(Boolean bool) {
this.expeditedShipping = bool;
}
public Boolean isOneDayShippingAvailable() {
return oneDayShippingAvailable;
}
public void setOneDayShippingAvailable(Boolean bool) {
this.oneDayShippingAvailable = bool;
}
public Integer getHandlingTime() {
return handlingTime;
}
public void setHandlingTime(Integer days) {
this.handlingTime = days;
}
}
Adding the line below points SimpleXML to the converter class
#Convert(ShippingInfoConverter.class)
The other change is removing the #Attribute annotation.
One last thing required is that your driver class needs to have AnnotationStrategy enabled
when serialising and deserialing your objects.
Serializer serializer = new Persister(new AnnotationStrategy());

Jackson JSONproperty not binding fields to variables

I am having issues getting Jackson to bind a JSON object's Properties to a POJO. It is returning the JSON's field names instead of the POJOs.
Here is the JSON object I am trying to bind:
"Data": {
"technical_description": "This is a description.",
"technical_version": "1.0",
"technical_last_modified": "2019-03-11T18:23:34",
"is_available_in_source": true,
"availability_description": null,
"is_oii": false,
"is_pii": false,
"grain_description": null,
"grain_attributes": [
"INSTANCE"
],
"source_objects": null
}
Here is the POJO I want it to bind too:
public class Specification {
#JsonProperty(value = "technical_description")
public String description;
#JsonProperty(value = "technical_version")
public String version;
#JsonProperty(value = "technical_last_modified")
#JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
public Date lastModified;
#JsonProperty(value = "is_available_in_source")
public Boolean availableInSource;
#JsonProperty(value = "availability_description")
public String availabilityDescription;
#JsonProperty(value = "is_oii")
public Boolean oii;
#JsonProperty(value = "is_pii")
public Boolean pii;
#JsonProperty(value = "grain_description")
public String grainDescription;
#JsonProperty(value = "grain_attributes")
public String[] grainAttributes;
#JsonProperty(value = "source_objects")
public String[] sourceObjects;
public Specification(){};
public Specification(String description, String version, Date lastModified, Boolean isAvailableInSource,
String availabilityDescription, Boolean isOii, Boolean isPii, String grainDescription,
String[] grainAttributes, String[] sourceObjects){
this.description=description;
this.version=version;
this.lastModified=lastModified;
this.availableInSource=isAvailableInSource;
this.availabilityDescription=availabilityDescription;
this.oii=isOii;
this.pii=isPii;
this.grainDescription=grainDescription;
this.grainAttributes=grainAttributes;
this.sourceObjects=sourceObjects;
};
}
The specification object is surfaced through a Detail object:
#Entity
public class Detail {
#Id
#Constraints.Required
public String id;
#Column(name = "specifications", updatable = false)
#Convert(converter = HashMapConverter.class)
public Map<String,Specification> technicalSpec;
...
}
Here is the converter I am using:
public class HashMapConverter implements AttributeConverter<Map<String, Specification>, String> {
private ObjectMapper objectMapper = new ObjectMapper().enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES);
#Override
public String convertToDatabaseColumn(Map<String, Specification> techSpecs){
String technicalSpecs = null;
try {
technicalSpecs = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(techSpecs);
} catch (final JsonProcessingException e) {
System.out.println(e);
}
return technicalSpecs;
}
#Override
public Map<String, Specification> convertToEntityAttribute(String techSpecJSON){
Map<String, Specification> techSpecs = null;
try {
techSpecs = objectMapper.readValue(techSpecJSON, objectMapper.getTypeFactory().constructMapType(HashMap.class, String.class, Specification.class));
} catch (final IOException e) {
System.out.println(e);
}
return techSpecs;
}
}
It will bind the object find through a custom converter, but it will return the same field names from the JSON object, i.e. I would like it to return description instead of technical_description.

How to get around HTTP Error 415 - Unsupported Media Type

I'm using Restet and I wanted to know if its possible if a ServerResource entity type is set, for example for this type of entity:
#XStreamAlias("role")
#ApiModel
public class Role {
private String entityId;
private String name;
#ApiModelProperty(required = false, value = "")
private List<String> aclRead;
#ApiModelProperty(required = false, value = "")
private List<String> acLWrite;
#ApiModelProperty(required = false, value = "")
private Boolean publicRead;
#ApiModelProperty(required = false, value = "")
private Boolean publicWrite;
public String getEntityId() {
return entityId;
}
public void setEntityId(String entityId) {
this.entityId = entityId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<String> getAclRead() {
return aclRead;
}
public void setAclRead(List<String> aclRead) {
this.aclRead = aclRead;
}
public List<String> getAcLWrite() {
return acLWrite;
}
public void setAcLWrite(List<String> acLWrite) {
this.acLWrite = acLWrite;
}
public Boolean getPublicRead() {
return publicRead;
}
public void setPublicRead(Boolean publicRead) {
this.publicRead = publicRead;
}
public Boolean getPublicWrite() {
return publicWrite;
}
public void setPublicWrite(Boolean publicWrite) {
this.publicWrite = publicWrite;
}
}
How can we be able to POST without the need of passing all the fields, for this example we only wanted to pass the "name" from the Client Request, but doing that throws 415 - Unsupported Media Type
In our client we only do pass this JSON
{
"role" : {
"name" : "AdminRole"
}
}
For the Get response of the Resource, the return type is Role also so the client will get all the fields, entityId, name, aclRead, aclWrite, publicRead and publicWrite through the Restlet marshalling.
The problem we have is that we cannot POST.
In the post request, the json data should only consist of the the entity parameters without the entity name as root key. So, your post request should look like this:
{
"name" : "AdminRole"
}

Categories