For Example, I have one POJO as shown below but it feeds into multiple operations but I do not want to create several identical POJO's just because the root element name changes for each operation. Hence I need one POJO but in such a way that I can dynamically change the Root Element Name.
#ToString
#MappedSuperclass
#lombok.Data
#XmlRootElement
#XmlAccessorType(XmlAccessType.NONE)
#EqualsAndHashCode(callSuper = false)
public class AmountOrAccountBlockOrUnblockRequest extends XmlBuilder implements SessionGenerator {
#JsonIgnore
#XmlElement
private String TargetBankVerificationNumber;
#JsonIgnore
#XmlElement
private String Narration;
#JsonProperty("amount")
#XmlElement(name = "Amount")
private String amount;
#JsonProperty("savingAccountNumber")
#XmlElement(name = "TargetAccountNumber")
private String targetAccountNumber;
#JsonIgnore
#XmlElement
private String ChannelCode;
#JsonProperty("unblockId")
#JsonIgnore
#XmlElement
private String ReferenceCode;
#JsonIgnore
#XmlElement
private String DestinationInstitutionCode;
#JsonIgnore
#XmlElement
private String TargetAccountName;
#XmlElement
private String SessionID;
#JsonIgnore
#XmlElement
private String ReasonCode;
// if account is still blocked or released
#JsonProperty("block")
private boolean blockUnblock;
#JsonProperty("blockUnblockReason")
private String blockUnblockReason;
#Override
public String toXmlString() {
return super.convertObjectToXmlString(this, this.getClass());
}
#Override
public void generateSessionID(HelperFacade helperFacade) {
setSessionID(helperFacade.generateSessionID(this.getDestinationInstitutionCode()));
}
}
This single POJO above will serve several operations but with a different Root Element Name for each operation for example,
<AmountUnblockRequest>
<SessionID>000001100913103301000000000001</SessionID>
<DestinationInstitutionCode>000002</DestinationInstitutionCode>
<ChannelCode>7</ChannelCode>
<ReferenceCode>xxxxxxxxxxxxxxx</ReferenceCode>
<TargetAccountName>Ajibade Oluwasegun</TargetAccountName>
<TargetBankVerificationNumber>1033000442</TargetBankVerificationNumber>
<TargetAccountNumber>2222002345</TargetAccountNumber>
<ReasonCode>0001</ReasonCode>
<Narration>Transfer from 000002 to 0YY</Narration>
<Amount>1000.00</Amount>
</AmountUnblockRequest>
and
<AmountBlockRequest>
<SessionID>000001100913103301000000000001</SessionID>
<DestinationInstitutionCode>000002</DestinationInstitutionCode>
<ChannelCode>7</ChannelCode>
<ReferenceCode>xxxxxxxxxxxxxxx</ReferenceCode>
<TargetAccountName>Ajibade Oluwasegun</TargetAccountName>
<TargetBankVerificationNumber>1033000442</TargetBankVerificationNumber>
<TargetAccountNumber>2222002345</TargetAccountNumber>
<ReasonCode>0001</ReasonCode>
<Narration>Transfer from 000002 to 0YY</Narration>
<Amount>1000.00</Amount>
</AmountBlockRequest>
I want to avoid the pain of having to create two identical classes all because the root element name will change.
You can use Declarative Stream Mapping (DSM) stream parsing library. You don't need to annotate your POJO both for XML and JSON.
You just define the mapping for data you want to extract from XML.
Here are mapping definitions for your XML.
result:
path: /(AmountBlockRequest|AmountUnblockRequest) // path is regex
type: object
fields:
TargetBankVerificationNumber:
Narration:
amount:
path: amount
xml:
path: Amount
targetAccountNumber:
path: targetAccountNumber
xml:
path: TargetAccountNumber
channelCode:
path: ChannelCode
referenceCode:
path: ReferenceCode
destinationInstitutionCode:
path: DestinationInstitutionCode
targetAccountName:
path: TargetAccountName
sessionID:
path: SessionID
reasonCode:
path: ReasonCode
blockUnblockReason:
path: BlockUnblockReason
Java Code to parse XML:
DSM dsm=new DSMBuilder(new File("path/to/mapping.yaml")).setType(DSMBuilder.TYPE.XML)..create(AmountOrAccountBlockOrUnblockRequest.class);;
// For json
//DSM dsm=new DSMBuilder(new File("path/to/mapping.yaml")).setType(DSMBuilder.TYPE.JSON)..create(AmountOrAccountBlockOrUnblockRequest.class);
AmountOrAccountBlockOrUnblockRequest result= (AmountOrAccountBlockOrUnblockRequest)dsm.toObject(xmlFileContent);
// json represntation fo result
dsm.getObjectMapper().writerWithDefaultPrettyPrinter().writeValue(System.out, object);
Here is output:
{
"amount" : "1000.00",
"targetAccountNumber" : "2222002345",
"blockUnblock" : false,
"blockUnblockReason" : null,
"sessionID" : "000001100913103301000000000001",
"reasonCode" : "0001",
"referenceCode" : "xxxxxxxxxxxxxxx",
"channelCode" : "7",
"narration" : null,
"targetBankVerificationNumber" : null,
"destinationInstitutionCode" : "000002",
"targetAccountName" : "Ajibade Oluwasegun"
}
Currently it is not support serialization.
Related
I'm trying to serialize and deserialize object with Jackson into XML, however I'm heaving troubles to do so...
My object:
Is an immutable class with getters and constructor only
It contains a list of sub-objects messages
I want to serialize this list wrapped in element <messages> and each message as a <message> element - as I understood I have to place #JsonProperty("message") #JacksonXmlElementWrapper(localName = "messages") annotations on getters, since when I place it on a constructor, it does not produce desired output (both are the same <message(s)>)
<response id="response-id">
<messages>
<message code="a"/>
<message code="b"/>
</messages>
</response>
I need to deserialize this structure, however as it's immutable object with final variables, I have to deserialize it through constructor.
Code snippet:
public class Playground {
public static void main(String[] args) throws JsonProcessingException {
Response response = new Response("response-id", List.of(new Message("a"), new Message("b")));
XmlMapper xmlObjectMapper = new XmlMapper(new XmlFactory());
xmlObjectMapper.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true);
final String value = xmlObjectMapper.writeValueAsString(response);
System.out.println(value);
assert ("<response id=\"response-id\"><messages><message code=\"a\"/><message "
+ "code=\"b\"/></messages></response>")
.equals(value) : "Desired format does not match!";
final Response deserializedValue = xmlObjectMapper.readValue(value, Response.class);
//final String deserialized = xmlObjectMapper.writeValueAsString(deserializedValue);
//assert value.equals(deserialized) : "Does not match";
}
}
#JsonInclude(Include.NON_EMPTY)
#JacksonXmlRootElement(localName = "response")
#JsonPropertyOrder({"id", "messages"})
class Response {
private final String id;
private final List<Message> messages;
#JsonCreator
public Response(
#JacksonXmlProperty(localName = "id", isAttribute = true) final String id,
#JsonProperty("message") final List<Message> messages) {
this.id = id;
this.messages = messages;
}
#JacksonXmlProperty(localName = "id", isAttribute = true)
public String getId() {
return id;
}
#JsonProperty("message")
#JacksonXmlElementWrapper(localName = "messages")
public List<Message> getMessages() {
return messages;
}
}
#JsonIgnoreProperties(ignoreUnknown = true)
#JsonInclude(Include.NON_EMPTY)
class Message {
private final String code;
public Message(#JacksonXmlProperty(localName = "code", isAttribute = true) final String code) {
this.code = code;
}
#JacksonXmlProperty(localName = "code", isAttribute = true)
public String getCode() {
return code;
}
}
When I try to deserialize it without messages annotated in the constructor I get exception: Invalid type definition for type 'Response': Argument #1 of constructor [constructor for 'Response' (2 args), annotations: {interface JsonCreator=#JsonCreator(mode=DEFAULT)} has no property name (and is not Injectable): can not use as property-based Creator
When I add #JsonProperty("message") annotation, I get exception: Invalid definition for property 'messages' (of type 'Response'): Could not find creator property with name 'messages' (known Creator properties: [id, message])
When I change it to #JsonProperty("messages") (plural), I get: Duplicate property 'messages' for [simple type, class Response]
When I add both, #JsonProperty("message") #JacksonXmlElementWrapper(localName = "messages"), it produces exception: Invalid definition for property 'messages' (of type 'Response'): Could not find creator property with name 'messages' (known Creator properties: [id, message]). (Nor any combination of message/messages in those two annotations works)
What am I doing wrong? What annotations do I need to use to retrieve desired XML output with it's deserialization?
For future googlers:
This seems to be related to jackson-dataformat-xml issue:
https://github.com/FasterXML/jackson-dataformat-xml/issues/187
There is also explanation and a workaround posted.
I have pojo which has many fields. I have set value some field. But when i create json, whole pojo create a json . here is the code i have used:
Pojo
public class BasicInfoModel {
private String client_id="";
private String father_name="";
private String mother_name="";
private String gendar="";
//Getter and setter
}
Repository code
public BasicInfoModel getBasicInfo() {
BasicInfoModel lm = new BasicInfoModel();
lm.setFather_name("Enamul Haque");
return lm;
}
Controller code
#RequestMapping(value = "/get_donar_basic_info", method = RequestMethod.POST, produces ="application/json")
public #ResponseBody BasicInfoModel getBasicinfo(){
return repository.getBasicInfo();
}
But my json is resposne like bellow:
{
"client_id": "",
"father_name": "Enamul Haque",
"mother_name": "",
"gendar": ""
}
I have set value to father_name but i have seen that i have found all the value of pojo fields. I want get only set value of father_name and ignor other value which is not set in repository.
My json look like bellow: I will display only father_name.how to display bellow like json from above code?
{
"father_name": "Enamul Haque"
}
Please help me..
Json include non null values to ignore null fields when serializing a java class
#JsonInclude(Include.NON_NULL)
Jackson allows controlling this behavior at either the class level:
#JsonInclude(Include.NON_NULL)
public class BasicInfoModel { ... }
at the field level:
public class BasicInfoModel {
private String client_id="";
#JsonInclude(Include.NON_NULL)
private String father_name="";
private String mother_name="";
private String gendar="";
//Getter and setter
}
from jackson 2.0 use here use
#JsonInclude(JsonInclude.Include.NON_NULL)
You can also ignore the empty values
#JsonInclude(JsonInclude.Include.NON_EMPTY)
Add the #JsonIgnoreProperties("fieldname") annotation to your POJO.
Or you can use #JsonIgnore before the name of the field you want to ignore while deserializing JSON. Example:
#JsonIgnore
#JsonProperty(value = "client_id")
#RequestMapping(value = "/get_donar_basic_info", method = RequestMethod.POST, produces ="application/json")
public #ResponseBody BasicInfoModel getBasicinfo(){
return repository.getBasicInfo();
}
You can ignore field at class level by using #JsonIgnoreProperties annotation and specifying the fields by name:
#JsonIgnoreProperties(value = { "client_id" })
public class BasicInfoModel {
private String client_id="";
private String father_name="";
private String mother_name="";
private String gendar="";
//Getter and setter
}
Or you can use #JsonIgnore annotation directly on the field.
public class BasicInfoModel {
#JsonIgnore
private String client_id="";
private String father_name="";
private String mother_name="";
private String gendar="";
//Getter and setter
}
You can read here more about this.
If i declare the namespace on the root element, like this:
#JacksonXmlRootElement(namespace = "urn:stackify:jacksonxml", localName = "PersonData")
public class Person {
private String id;
private String name;
private String note;
}
It produces:
<PersonData xmlns="urn:stackify:jacksonxml">
<id xmlns="">12345</id>
<name xmlns="">Graham</name>
<note xmlns="">Hello</note>
</PersonData>
But I want the namespace only on the root element. The xmlns attribute should not appear on child elements.
How can i archive this?
There is a workaround which I found more elegant for me.
You may define constant for your namespace like this:
#JacksonXmlRootElement(localName = "PersonData")
public class Person {
#JacksonXmlProperty(isAttribute = true)
private final String xmlns = "urn:stackify:jacksonxml";
private String id;
private String name;
private String note;
}
You need to specify the same namespace as the root element in each attribute:
#JacksonXmlRootElement(namespace = "urn:stackify:jacksonxml", localName = "PersonData")
public class Person {
#JacksonXmlProperty(namespace = "urn:stackify:jacksonxml")
private String id;
#JacksonXmlProperty(namespace = "urn:stackify:jacksonxml")
private String name;
#JacksonXmlProperty(namespace = "urn:stackify:jacksonxml")
private String note;
}
Its a bit tiresome, but its the only way I found to avoid the unnecessary namespaces.
Also works well with immutables library and json annotations (if you need to serialize/deserialize both in JSON and in XML)
#Value.Immutable
#JsonRootName(value = "PersonData", namespace = "urn:stackify:jacksonxml")
public interface Person extends Serializable {
}
I want to parse a json to an object which gives me details of every entity attached to a bank.
My json looks like :
{
"href" : "abc",
"configurations" :
[
{
"type" : "bank-customer",
"properties" : {
"cust-name" : "foo",
"acc-no" : "12345"
}
},
{
"type" : "bank-employee",
"properties" : {
"empl-name" : "foo",
"empl-no" : "12345"
}
}
]
}
The properties for various entity "type" is different.
Creating a pojo for this is the challenge. My properties.java will have to include all properties irrespective of the type of the property :
public class Configurations {
#SerializedName("type")
#Expose
private String entityType;
#SerializedName("properties")
#Expose
private Properties properties;
}
public class Properties {
#SerializedName("cust-name")
#Expose
private String custName;
#SerializedName("empl-no")
#Expose
private String emplNo;
#SerializedName("empl-name")
#Expose
private String emplName;
#SerializedName("acc-no")
#Expose
private String accNo;
}
This is painful when I have a lot of entity types and property per entity type. Is there any other way I can parse this json into different property objects for different entity types? I am using gson to parse the JSON
Note : I can't make any changes to the json itself.
I completely agree with mauros answer.
But you can also create a interface hierarchy and implement Instance Creator.
Its can be easily solved by using alternate keyword from #SerializedName annotation.
public class Properties {
#SerializedName(value="cust-name", alternate={"empl-name", "user-name"})
#Expose
private String name;
#SerializedName("acc-no", alternate={"empl-no", "user-id"})
#Expose
private String id;
//setter and getters
}
I am currently trying to develop a REST client that communicates with a certain online service. This online service returns some JSON responses which I wish to map to Java objects using Jackson.
An example of a JSON response would be:
{
"id" : 1,
"fields" : [ {
"type" : "anniversary",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "birthday",
"value" : {
"day" : 1,
"month" : 1,
"year" : 1970
}
}, {
"type" : "simple",
"value" : "simple string"
},{
"type": "name",
"value": {
"firstName": "Joe",
"lastName": "Brown"
}
} ]
}
NOTE the following:
this structure contains a simple id, and a collection of Field instances, each having a type and a value
the value structure is determined by the external property type
in the example given, there are 3 types -> date, name, and single value string
the birthday and anniversary types both match the date structure
there are several types which map to a single value string, like email type, twitterId type, company type etc.
My problem is that I do not seem to be able to correctly map this structure to the Java objects
Here's what I've done so far. The following are the classes and their Jackson annotations(the getters and setters are omitted).
public class Contact {
private int id;
private List<Field> fields;
}
public class Field {
private FieldType type;
private FieldValue value;
}
public enum FieldType {
EMAIL, NICKNAME, NAME, ADDRESS, BIRTHDAY, ANNIVERSARY
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type",
defaultImpl = SingleFieldValue.class)
#JsonSubTypes({
#JsonSubTypes.Type(value = NameFieldValue.class, name = "name"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "anniversary"),
#JsonSubTypes.Type(value = DateFieldValue.class, name = "birthday"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "nickname"),
#JsonSubTypes.Type(value = SingleFieldValue.class, name = "email"),
//other types that map to SingleFieldValue
})
public abstract FieldValue {
}
public class NameFieldValue extends FieldValue {
private String firstName;
private String lastName;
}
public class DateFieldValue extends FieldValue {
private int day;
private int month;
private int year;
}
public class SingleFieldValue extends FieldValue {
private String value;
}
The ObjectMapper does not contain any configuration, the default configuration is used.
What suggestions do you have to correctly map these? I would like to avoid making custom deserializers and just traverse Json objects, like JsonNode.
NOTE: I apologize in advance for any lack of information to make this issue clear enough. Please state any problems with my formulation.
You have used an abstract class on the FieldValue level to use it in FIeld class. In that case you can construct the object with type=email and value=address which can lead to some issues...
I would recommend to create a specific classes for every type with specific FieldValue type.
The following code is serializing/deserializing JSON from/to required format from/to POJO:
public class Main {
String json = "{\"id\":1,\"fields\":[{\"type\":\"SIMPLE\",\"value\":\"Simple Value\"},{\"type\":\"NAME\",\"value\":{\"firstName\":\"first name\",\"lastName\":\"last name\"}}]}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(generate());
System.out.println(json);
System.out.println(objectMapper.readValue(json, Contact.class));
}
private static Contact generate() {
SimpleField simpleField = SimpleField.builder().type(FieldType.SIMPLE).value("Simple Value").build();
NameFieldValue nameFieldValue = NameFieldValue.builder().firstName("first name").lastName("last name").build();
NameField nameField = NameField.builder().type(FieldType.NAME).value(nameFieldValue).build();
return Contact.builder().id(1).fields(Arrays.asList(simpleField, nameField)).build();
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXTERNAL_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = SimpleField.class, name = "SIMPLE"),
#JsonSubTypes.Type(value = NameField.class, name = "NAME")
})
interface Field {
FieldType getType();
Object getValue();
}
enum FieldType {
SIMPLE, NAME
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class Contact {
private int id;
private List<Field> fields;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class SimpleField implements Field {
private FieldType type;
private String value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public String getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameField implements Field {
private FieldType type;
private NameFieldValue value;
#Override
public FieldType getType() {
return this.type;
}
#Override
public Object getValue() {
return this.value;
}
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
class NameFieldValue {
private String firstName;
private String lastName;
}
I have used lombok library here just to minimize the code and avoiding creating getters/setters as well as constructors. You can delete lombok annotations and add getters/setters/constructors and code will work the same.
So, the idea is that you have a Contact class (which is root of your JSON) with a list of Fields (where Field is an interface). Every Field type has own implementation like NameField implements Field and has NameFieldValue as a property. The trick here is that you can change getValue() method declaration and declare that it returns the common interface or Object (I used Object but interface will work as well).
This solution doesn't require any custom serializers/deserializers and easy in maintenance.