How do you deserialize XML without multiple wrapper classes? - java

I have some XML that I want to turn into an object using Jackson FasterXML. The XML looks like this:
<services>
<service id="1" name="test">
<addresses>
<postalAddress id="2" line1="123 Fake Street" city="Springfield" />
</addresses>
</service>
</services>
I am deserializing this as an object successfully with these classes:
JsonIgnoreProperties(ignoreUnknown = true)
#JacksonXmlRootElement(localName = "services")
public class ServiceWrapper {
#JacksonXmlProperty(localName = "service")
private Service service;
//Getters and setters [...]
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Service {
#JacksonXmlProperty(isAttribute = true)
private int id;
#JacksonXmlProperty(isAttribute = true)
private String name;
#JacksonXmlProperty(localName = "addresses")
private AddressWrapper addresses;
//Getters and setters [...]
}
public class AddressWrapper {
#JacksonXmlProperty(localName = "postalAddress")
private List<Address> addresses;
//Getters and setters [...]
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Address {
#JacksonXmlProperty(isAttribute = true, localName = "id")
private int id;
#JacksonXmlProperty(isAttribute = true, localName = "line1")
private int address1;
#JacksonXmlProperty(isAttribute = true, localName = "city")
private int city;
//Getters and setters [...]
}
And the code to do the mapping:
JacksonXmlModule module = new JacksonXmlModule();
module.setDefaultUseWrapper(false);
ObjectMapper mapper = new XmlMapper(module);
mapper.registerModule(new JSR310Module());
mapper.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true);
ServiceWrapper serviceWrapper = mapper.readValue(xmlString, ServiceWrapper.class);
return serviceWrapper.getService();
This all works fine, but it's a lot of overhead and ugly code to have the ServiceWrapper and AddressWrapper classes; when really all I want is the data in the <service> node and <postalAddress> node. Is it possible to tell the Jackson object to directly populate a list of Addresses in my Service class without having the AddressWrapper class to represent the <addresses> node? Similarly to take the entire xml and populate a Service class directly without needing a ServiceWrapper class?

The normal way to avoid writing/maintaining such code is to use JAXB to generate the Java code (with appropriate annotations). This process uses an XML schema (.xsd) as the input, with an optional bindings file (.xjb) to customize the generated code.
It looks like many of the JAXB annotations are supported by Jackson.
I will also note that JAXB code generator (xjc) supports plugins that allow you to do pretty much anything you want to augment the generated code (e.g., with additional methods or annotations).

Related

Java - XML POJO

I'm trying to create an XMLElement Pojo that would then be converted to a string that would suffice:
result = "<layerA><layerB><itemA><someProperty>100.00</someProperty>......</layerA>
<layerA>
<layerB>
<itemA>
<someProperty>100.00</someProperty>
</itemA>
<itemB>
<differentProperty>AAA</differentProperty>
</itemB>
</layerB>
</layerA>
public class layerA
#XmlElement(required = true)
private LayerB layerB;
public class layerB
#XmlElement(required = true)
private LayerB layerB;
#XmlElement(required = true)
private List<Object> items; ?????????
There can be itemA, itemB, itemC, itemD etc --> in the xml they are classified via name itemA, itemB, and can have different properties.
Wondering if there's a quick way to construct that xml, ultimately it would be a string.

Include/exclude Attributes in json response from application.yml

I am using JHipster(spring boot) to generate my project. I would like to hide/show fields in JSON from application.yml. for exemple:
I have the following class
#Entity
#Table(name = "port")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Port implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "city")
private String city;
#Column(name = "description")
private String description;
//getters & setters
}
My GET method return a response like:
{
"id": 1,
"city": "boston",
"description": "test test"
}
I would like to be able to include/exclude some fields from application.yml (since i don't have application.properties) otherwise to have something like:
//application.yml
include: ['city']
exclude: ['description']
in this exemple my json should look like:
{
"id": 1,
"city": "boston",
}
for exemple if I have 40 fields and I need to hide 10 and show 30 I just want to put the 10 I want to hide in exclude in application.yml without go everytime to change the code. I guess #jsonignore hide fields but I don't know how to do it from application.yml
Sorry for not explaining well. I hope it's clear.
Thank you in advance for any suggestion or solution to do something similar
Spring boot by default uses Jackson JSON library to serialize your classes to Json. In that library there is an annotation #JsonIgnore which is used precisely for the purpose to tell Json engine to egnore a particular property from serialization/de-serialization. So, lets say in your entity Port you would want to exclude property city from showing. All you have to do is to annotate that property (or its getter method) with #JsonIgnore annotation:
#Column(name = "city")
#JsonIgnore
private String city;
You can try to create a hashmap in your controller to manage your HTTP response.
Map<String, Object> map = new HashMap<>();
map.put("id", Port.getId());
map.put("city", Port.getCity());
return map;
Basically you don't expose your Port entity in your REST controller, you expose a DTO (Data Transfer Object) that you value from your entity in service layer using a simple class (e.g PortMapper). PortDTO could also be a Map as suggested in other answer.
Your service layer can then use a configuration object (e.g. PortMapperConfiguration) that is valued from application.yml and used by PortMapper to conditionally call PortDTO setters from Port getters.
#ConfigurationProperties(prefix = "mapper", ignoreUnknownFields = false)
public class PortMapperConfiguration {
private List<String> include;
private List<String> exclude;
// getters/setters
}
#Service
public class PortMapper {
private PortMapperConfiguration configuration;
public PortMapper(PortMapperConfiguration configuration) {
this.configuration = configuration;
}
public PortDTO toDto(Port port) {
PortDTO dto = new PortDTO();
// Fill DTO based on configuration
return dto;
}
}

How do I unmarshall a repeated XML tag having same name but different structure inside another XML tag using Jaxb or jackson

XML which I want to unmarshall to java object:
<API Name="A">
<Input>
<Shipment
EnterpriseCode="ABC"
SellerOrganizationCode="6528"
ShipNode=""
ShipmentKey="202105171041273117421546">
<ShipmentLines>
<ShipmentLine BackroomPickedQuantity="2.0"
ShipmentLineKey="41273117421545">
<Extn
ExtnStagingLocation="location1"/>
</ShipmentLine>
</ShipmentLines>
<Extn ExtnInPickupLocker="N"
ExtnPickerId="1531685"
ExtnPickingHasStartedFlag="Y"
ExtnStagedByUserName="Windows and Walls"
ExtnStagingLocation=""/>
</Shipment>
</Input>
</API>
Below is the EXTN and EXTN1 java classes which I trying to map with XML have used JAXB and JACKSON to identify and map the variables with EXTN tag to java class Extn and Extn1.
#XmlAccessorType(XmlAccessType.FIELD)
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class Extn {
#JsonProperty(value = "ExtnStagingLocation")
#JacksonXmlProperty(isAttribute = true)
private String extnStagingLocation = "location1";
#JsonProperty(value = "ExtnInPickupLocker")
#XmlAttribute(name = "ExtnInPickupLocker", required = true)
private String extnInPickupLocker = "N";
#JsonProperty(value = "ExtnPickerId")
#JacksonXmlProperty(isAttribute = true)
private String extnPickerId = "1531685";
#JsonProperty(value = "ExtnPickingHasStartedFlag")
#JacksonXmlProperty(isAttribute = true)
private String extnPickingHasStartedFlag = "Y";
#JsonProperty(value = "ExtnStagedByUserName")
#JacksonXmlProperty(isAttribute = true)
private String ExtnStagedByUserName = "Windows and Walls";
}
#XmlAccessorType(XmlAccessType.FIELD)
#Data
#ToString
#NoArgsConstructor
#AllArgsConstructor
public static class Extn1 {
#JsonProperty(value = "ExtnStagingLocation")
#JacksonXmlProperty(isAttribute = true)
private String extnStagingLocation = "location1";
}
How do I Create a single class EXTN and then pass the desired attributes at different level while creating java object, rather creating different java class Extn and Extn1 and map the attributes inside the Extn tag
Note: Have created EXTN and EXTN1 to pass different attribute and it's value at different place

Serialization with Jackson XmlMapper

I'm trying to serialize object to xml string with Jackson XmlMapper. My object is:
#JacksonXmlRootElement(namespace = "http://www.w3.org/2001/XMLSchema", localName = "PersonRO")
public class PersonInfo {
#JacksonXmlProperty(localName = "PersonID")
private String personId;
#JacksonXmlProperty(localName = "ReturnCode")
private Integer errorCode;
// getters, setters
}
I need to achieve following xml in output:
<PersonRO xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<PersonID>00000000000001</PersonID>
<ReturnCode>150</ReturnCode>
</PersonRO>
The task seems easy, but first of all I have a problem with achieving multiple namespases (xmlns:xsd, xmlns:xsi), and also have empty namespaces for fields, although I don't need them at all.
So far my result is:
<PersonRO xmlns="http://www.w3.org/2001/XMLSchema">
<PersonID xmlns="">00000000000001</PersonID>
<ReturnCode xmlns="">150</ReturnCode>
</PersonRO>
So, how can I achieve exactly the same result as above, using Jackson XmlMapper?
(I've seen that you can configure XmlFactory, etc., but can not do it properly...)
If you need any clarification, please let me know and thank you in advance.
I have found the answer:
#JacksonXmlRootElement(localName = "PersonRO")
public class PersonInfo {
#JacksonXmlProperty(isAttribute = true, localName = "xmlns:xsd")
private final String xmlnsXsd = "http://www.w3.org/2001/XMLSchema";
#JacksonXmlProperty(isAttribute = true, localName = "xmlns:xsi")
private final String xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance";
#JacksonXmlProperty(localName = "PersonID")
private String personId;
#JacksonXmlProperty(localName = "ReturnCode")
private Integer errorCode;
// getters, setters
}

Caused by: com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "Status"

I am receiving following error message, I have Status class but it is not being recognized. I've got no idea how to proceed and could not find an answer online.
Error
org.springframework.http.converter.HttpMessageNotReadableException: Could
not read JSON: Unrecognized field "Status" (class
com.myproject.ticket.EventsResponse), not marked as ignorable (3 known
properties: "events", "status", "page"])
....
Caused by:
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException:
Unrecognized field "Status" (class com.myproject.ticket.EventsResponse),
not marked as ignorable (3 known properties: "events", "status", "page"])
EventsResponse
#XmlRootElement(name = "EventsResponse")
#XmlAccessorType(XmlAccessType.FIELD)
public class EventsResponse {
#XmlElement(name = "Status")
private Status status;
#XmlElement(name = "Paging")
private Page page;
#XmlElementWrapper(name="Events")
#XmlElement(name = "Event")
private List<Event> events;
.....
Status
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class Status {
#XmlElement(name = "Version")
private double version;
#XmlElement(name = "TimeStampUtc")
private Date timeStampUtc;
#XmlElement(name = "Code")
private int code;
#XmlElement(name = "Message")
private String message;
#XmlElement(name = "Details")
private String details;
Response
<EventsResponse xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Status>
<Version>2.0</Version>
<TimeStampUtc>2016-06-11T09:32:21</TimeStampUtc>
<Code>0</Code>
<Message>Success</Message>
<Details />
</Status>
<Paging>
<PageNumber>1</PageNumber>
<PageSize>50</PageSize>
<PageResultCount>15</PageResultCount>
<TotalResultCount>15</TotalResultCount>
<TotalPageCount>1</TotalPageCount>
</Paging>
<Events>
<Event>
I added following to Status but I am still receiving the same error.
#XmlElement(name = "Status")
#JacksonXmlProperty(localName = "Status")
private Status status;
I failed to reconstruct your issue.
I created a test project github here that has Jackson configuration and JAXB annotations that meet your needs.
I added dependencies to jackson-dataformat-xml and woodstox-core-asl as your Stax implementations (in my test project I am using Jackson 2.6.6. ,Spring 4.2.6)
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.6.6</version>
</dependency>
<dependency>
<groupId>org.codehaus.woodstox</groupId>
<artifactId>woodstox-core-asl</artifactId>
<version>4.4.1</version>
</dependency>
Configure the Jackson2ObjectMapperBuilder to use both Jackson and JAXB annotations. This is a Spring-boot example to convert to Simple Spring-MVC look here
#SpringBootApplication
public class EventAppConfiguration {
public static void main(String[] args) {
SpringApplication.run(EventAppConfiguration.class, args);
}
#Bean
public Jackson2ObjectMapperBuilder jacksonBuilder() {
Jackson2ObjectMapperBuilder b = new Jackson2ObjectMapperBuilder();
b.indentOutput(true)
//Enable Introspects for both Jackson and JAXB annotation
.annotationIntrospector(introspector())
//Use CamelCase naming
.propertyNamingStrategy(PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss"));
return b;
}
#Bean
public AnnotationIntrospector introspector(){
AnnotationIntrospector primary = new JacksonAnnotationIntrospector();
AnnotationIntrospector secondary = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
AnnotationIntrospector pair = AnnotationIntrospector.pair(primary, secondary);
return pair;
}
}
Note the use of
PropertyNamingStrategy.PASCAL_CASE_TO_CAMEL_CASE
it will save you the need to specify alternative naming for first letter capitalization and will require the use for JAXB annotation only for warping and renaming for example my EventsResponse will look like:
#XmlRootElement
#XmlAccessorType(XmlAccessType.FIELD)
public class EventsResponse {
private Status status;
#XmlElement(name = "Paging")
private Page page;
#XmlElementWrapper(name = "Events")
#XmlElement(name = "Event")
private List<Event> events;
...
}
You have two options, assuming you are using Jackson to deserialize your XML objects. The simplest is to use Jackson's own XML annotations instead of or as well as the JAXB #XmlElement annotations. For example:
#XmlElement(name = "Status")
#JacksonXmlProperty(localName = "Status")
private Status status;
(The #XmlElement annotation is in the jackson-dataformat-xml package in Maven - the version should match your other Jackson package versions.)
The alternative is to register an AnnotationIntrospector as part of your deserialization chain - ie. (from a unit test):
XmlMapper mapper = new XmlMapper();
AnnotationIntrospector aiJaxb = new JaxbAnnotationIntrospector(TypeFactory.defaultInstance());
mapper.setAnnotationIntrospector(aiJaxb);
// EVENTS_RESPONSE is the incoming XML
EventsResponse response = mapper.readValue(EVENTS_RESPONSE, EventsResponse.class);
This recognises the #XmlElement annotation. There are more details in this answer if you need to include this as part of a Spring configuration, for example.
(In order to use the JaxbAnnotationIntrospector class, you will need the jackson-module-jaxb-annotation module from Maven.)

Categories