Unable to deserialize non formatted child node in XML with Jackson - java

I am converting Xml to Pojo object with help of jackson, But due to different format of xml it's not converting all fields to Pojo, Please follow for xml structure and mapping Pojo,
Here I want to deserialize 13 value of iphone.
XML- <PhoneDetails> <Iphone os="ios">13</Iphone> </PhoneDetails>
POJO
#lombok.Data
public class PhoneDetails {
#JacksonXmlProperty(localName = "Iphone")
private Iphone iphoneDetails;
}
#lombok.Data
public class Iphone {
private String os;
private String iphone;
}
main method
public class Demo {
public static void main(String[] args) throws Exception {
XmlMapper xmlMapper = new XmlMapper();
xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
xmlMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
xmlMapper.configure(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME, false);
File availsContract = ResourceUtils.getFile("classpath:to_deserialize.xml");
String content = new String(Files.readAllBytes(availsContract.toPath()));
PhoneDetails productDetail = xmlMapper.readValue(content, PhoneDetails.class);
System.out.println("Product Details :" + productDetail);
}
}
Please reply with solutions.

Welcome to Stack Overflow, a simple xmlMapper mapper is enough to deserialize your xml; you have to use the JacksonXmlText annotation to deserialize the 13 value from the <PhoneDetails> <Iphone os="ios">13</Iphone> </PhoneDetails> xml file into your String iphone property:
#Data
public class PhoneDetails {
#JacksonXmlProperty(localName = "Iphone")
private Iphone iphoneDetails;
}
#Data
public class Iphone {
private String os;
#JacksonXmlText
private String iphone;
}
//Input xmlfile:
//<PhoneDetails> <Iphone os="ios">13</Iphone> </PhoneDetails>
XmlMapper xmlMapper = new XmlMapper();
PhoneDetails phoneDetails = xmlMapper.readValue(xml, PhoneDetails.class);
//ok, it prints PhoneDetails(iphoneDetails=Iphone(os=ios, iphone=13))
System.out.print(phoneDetails);

Related

Jackson deserialization using JsonParser distinguish between the direct object and objects within array

I am using the Jackson for the Deseilization of the JSON. The Deseilization works perfectly for a JSON with CustomerDocument. However, I have a new requirement in which I need to find whether provided JSON has CustomerDocument or just Customer.
I am able to develop the logic for both but the problem is that when I try to merge it won't work for CustomerDocument. I am looking for a solution that would work for both. All I would like to do is build the logic to differentiate the incoming JSON based on customerDocument and single Customer.
Following is the CustomerDocument JSON:
{
"isA": "CustomerDocument",
"customerList": [
{
"isA": "Customer",
"name": "Batman",
"age": "2008"
}
]
}
Customer.class:
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer {
private String isA;
private String name;
private String age;
}
JacksonMain:
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, Customer.class);
System.out.println(event.toString());
}
}
}
The above code works perfectly and produces the following result:
Customer(isA=Customer, name=Batman, age=2008)
Scenario-2
Now user can provide the direct customer object without the customerDocument. Something like this:
{
"isA": "Customer",
"name": "Superman",
"age": "2013"
}
'Customer.class' would remain the same and JacksonMain would be modified to:
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
final JsonNode jsonNode = jsonParser.readValueAsTree();
final String inputType = jsonNode.get("isA").asText();
if (inputType.equalsIgnoreCase("Customer")) {
Object singleCustomer = objectMapper.treeToValue(jsonNode, Customer.class);
System.out.println(singleCustomer.toString());
} else if (inputType.equalsIgnoreCase("CustomerDocument")) {
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, Customer.class);
System.out.println(event.toString());
}
}
}
}
For a single CUstomer this would produce the following result:
Customer(isA=Customer, name=Superman, age=2013)
For the same code now if I provide the CustomerDocument (the first JSON) then it would not work and fail with error:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.equals(Object)" because the return value of "com.fasterxml.jackson.core.JsonParser.getText()" is null
at stackover.JacksonMain.main(JacksonMain.java:32)
I know this issue is happening because of the line
final JsonNode jsonNode = jsonParser.readValueAsTree();
Can someone please explain how to make the code work for both the type of JSON customerDocument and just single Customer using Jackson? I just want to differentiate whether incoming JSON is customerDocument or single Customer. Any help would be really appreciated.
I want to use Jackson to make the differentiation between both the input.
It would be great if there is no need to create any additional classes. However, it's fine if there is a need to create an interface to achieve this.
My CustomerList can be very huge so I am reading one by one so it does not make much memory. hence I do not have the CustomerDocument class with List<Customer> rather I am looking over it and mapping one by one.
Well you can use Jackson sub type to de-serialize between Customer and CustomerDocument.
Something like following,
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"}]}";
// String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
ObjectMapper om = new ObjectMapper();
BaseResponse baseResponse = om.readValue(s, BaseResponse.class);
if (baseResponse instanceof CustomerDocument) {
CustomerDocument cd = (CustomerDocument) baseResponse;
System.out.println("Inside If..");
cd.getCustomerList().forEach(System.out::println);
} else if (baseResponse instanceof Customer) {
System.out.println("Inside Else If..");
Customer cs = (Customer) baseResponse;
System.out.println(cs);;
}
}
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonSubTypes({
#JsonSubTypes.Type(value = Customer.class, name = "Customer"),
#JsonSubTypes.Type(value = CustomerDocument.class, name = "CustomerDocument")})
interface BaseResponse {}
#Getter
#Setter
#ToString
class Customer implements BaseResponse{
private String isA;
private String name;
private String age;
}
#Getter
#Setter
#ToString
class CustomerDocument implements BaseResponse{
private String isA;
private List<Customer> customerList;
}
PS - Uncomment the string in main method to illustrate the other case.
Update
public class Main {
public static void main(String[] args) throws IOException {
String s = "{\"isA\":\"CustomerDocument\",\"customerList\":[{\"isA\":\"Customer\",\"name\":\"Batman\",\"age\":\"2008\"},{\"isA\":\"Customer B\",\"name\":\"Superman\",\"age\":\"2013\"}]}";
// String s = "{\"isA\":\"Customer\",\"name\":\"Superman\",\"age\":\"2013\"}";
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(s);
String type = node.get("isA").asText();
if (type.equals("Customer")) {
Customer c = om.readValue(s, Customer.class);
System.out.println(c);
} else if (type.equals("CustomerDocument")) {
JsonNode nextNode = node.path("customerList");
List<Customer> cl = om.convertValue(nextNode, new TypeReference<List<Customer>>() {});
cl.forEach(System.out::println);
}
}
}
#Getter
#Setter
#ToString
class Customer {
private String isA;
private String name;
private String age;
}
Following worked for me based on the above provided the answer:
BaseResponse interface:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonSubTypes({
#JsonSubTypes.Type(value = Customer.class, name = "Customer")})
public interface BaseResponse {
}
Customer class:
#Data
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, visible = true, property = "isA")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Customer implements BaseResponse {
private String isA;
private String name;
private String age;
}
public class JacksonMain {
public static void main(String[] args) throws IOException {
final InputStream jsonStream = JacksonMain.class.getClassLoader().getResourceAsStream("Customer.json");
final JsonParser jsonParser = new JsonFactory().createParser(jsonStream);
final ObjectMapper objectMapper = new ObjectMapper();
jsonParser.setCodec(objectMapper);
//Goto the start of the document
jsonParser.nextToken();
try {
BaseResponse baseResponse = objectMapper.readValue(jsonParser, BaseResponse.class);
System.out.println("SINGLE EVENT INPUT");
System.out.println(baseResponse.toString());
} catch (Exception e) {
System.out.println("LIST OF CUSTOMER INPUT");
//Go until the customerList has been reached
while (!jsonParser.getText().equals("customerList")) {
jsonParser.nextToken();
}
jsonParser.nextToken();
//Loop through each object within the customerList and deserilize them
while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
final JsonNode customerNode = jsonParser.readValueAsTree();
final String eventType = customerNode.get("isA").asText();
Object event = objectMapper.treeToValue(customerNode, BaseResponse.class);
System.out.println(event.toString());
}
}
}
}

Read part of yaml in Java using Jackson

if I have the following yaml (which I found online) representing a java Order class, order.yaml:
orderNo: A001
customerName: Customer, Joe
orderLines:
- item: No. 9 Sprockets
quantity: 12
unitPrice: 1.23
- item: Widget (10mm)
quantity: 4
unitPrice: 3.45
I was able to use
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Order order = objectMapper.readValue(new File(<path_to_order>), Order.class);
But this means that I need to define orderNo and orderLines in advance... If I have a giant yaml with a bunch of nested properties this can get really annoying. What if I want a class than can read one property or a class that can read another property and "ignore" other ones? Is that even possible? That way I could just specify which java object I want without necessarily having to recursively define every property of the yaml. Thank you!
The Map approach will lose you the type safety. There's no need to define every single property. You can use the Json annotations just fine with YAML too, it's just a historical leftover that it is called Json. What you are looking for is #JsonIgnoreProperties(ignoreUnknown = true).
If you don't like to specify the Annotation for every class, use objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);.
public static void main(String[] args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Order order = objectMapper.readValue(new File("foo.yml"), Order.class);
System.out.println(order.getOrderLines().get(0).getItem());
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class Order {
private String orderNo;
private List<OrderLine> orderLines;
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public List<OrderLine> getOrderLines() {
return orderLines;
}
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
#JsonIgnoreProperties(ignoreUnknown = true)
static class OrderLine {
private String item;
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
}
}
You can read the json in a Map and then retrieve whatever you want from there
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
Map<String,Object> jsonMap = objectMapper.readValue(new File(<path_to_order>), Map.class);

Ignore fields only in XML but not json in spring boot (xml Mapper)

How to ignore some fields while converting POJO to XML using XMLMapper but not in JSON.
public String getXmlInString(String rootName, Object debtReport) {
XmlMapper xmlMapper = new XmlMapper();
return xmlMapper.writer().withRootName(rootName).withDefaultPrettyPrinter().writeValueAsString(debtReport);
}
POJO Class
Class Employee {
Long id;
String name;
LocalDate dob;
}
The expected output in JSON
{
"id": 1,
"name": "Thirumal",
"dob": "02-04-1991"
}
The expected output in XML (Need to ignore ID)
<Employee>
<name>Thirumal</name>
<dob>02-04-1991</dob>
</Employee>
You can achieve that using JsonView
First declare Views class with two "profiles" - default (only Default fields are serialized) and json-only (both Default and Json fields are serialized):
public class Views {
public static class Json extends Default {
}
public static class Default {
}
}
Then mark always visible fields with Default-view and ID field with Json view:
public class Employee {
#JsonView(Views.Json.class)
Long id;
#JsonView(Views.Default.class)
String name;
#JsonView(Views.Default.class)
String dob;
}
Then instruct mapper to respect given appropriate view during serialization:
#Test
public void test() throws JsonProcessingException {
Employee emp = new Employee();
emp.id = 1L;
emp.name = "John Doe";
emp.dob = "1994-03-02";
// JSON with ID
String json = new ObjectMapper()
.writerWithView(Views.Json.class)
.writeValueAsString(emp);
System.out.println("JSON: " + json);
// XML without ID
String xml = new XmlMapper()
.writerWithView(Views.Default.class)
.writeValueAsString(emp);
System.out.println("XML: " + xml);
}
Finally the output is:
JSON: {"id":1,"name":"John Doe","dob":"1994-03-02"}
XML: <Employee><name>John Doe</name><dob>1994-03-02</dob></Employee>

How to convert embedded JSON String inside JSON to java object?

I have the below json, where the body key contains a value which is a string representation of a JSON object, how do I convert it to a Java Object ?
I can extract the body value by converting the JSON to a Map, but I don't know how I should proceed from there
input.json file
{
"body": "{\n\t\"username\": \"TestUser\",\n\t\"password\": \"TestPassword\"\n}"
}
The User POJO is as below,
class User {
private String username;
private String password;
... getters, setters and no-arg constructor
}
My code looks something like this, I need to implement convertToUser function
public static void main(String[] args) {
String jsonContent = readJsonFile("input.json");
String escapedJsonBody = getBody(s);
User user = convertToUser(escapedJsonBody, User.class);
}
I am already using jackson java library, any insights on doing this with jackson is highly appreciated.
One way to do it is to create DTOs and converter. Having DTOs like (i have nested the class declarations jsut to save space in answer):
#Getter #Setter
public class Input { // this level maps to the whole input.json
#JsonDeserialize(using = BodyDeserializer.class) // custom deserializer below
private Body body; // this is the body-attribute in JSON
#Getter #Setter
public static class Body {
private User user;
#Getter #Setter
public static class User {
private String username;
private String password;
}
}
}
the converter:
public class BodyDeserializer extends JsonDeserializer<Body> {
private ObjectMapper om = new ObjectMapper(); // this is to read the user from string
#Override
public Body deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String embedded = p.readValueAs(String.class);
Body body = new Body();
body.setUser(om.readValue(embedded, User.class)); // here is the trick
return body;
}
}
Use like:
ObjectMapper om = new ObjectMapper();
String input = "{\"body\": \"{\\n\\t\\\"username\\\": \\\"TestUser\\\",\\n\\t\\\"password\\\": \\\"TestPassword\\\"\\n}\"}";
Input r = om.readValue(input, Input.class);
This way the conversion happens in generic way only con might be that you do not like to create DTOs and dig the user like Input.getBody().getUser();
To convert a JSON String to a java pojo you can use Jackson's ObjectMapper class that will assist you to do this.
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.readValue(inputJson, User.class);
More info can be found on Jackson's github page

Override #JsonInclude(Include.NON_NULL) from POJO in ObjectMapper

Question: Is it possible to override Include.NON_NULL defined in the POJO while creating the ObjectMapper?
Explanation:
Suppose I have a POJO as below:
#JsonInclude(Include.NON_NULL)
class POJO {
String name;
String description;
//Constructors, Getters & Setters, etc
}
And a test class as below:
class Main {
public static void main(String args[]) {
POJO p = new POJO();
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.setSerializationInclusion(Include.ALWAYS)
.writerWithDefaultPrettyPrinter()
.writeValueAsString(p);
//jsonString should contain empty name & description fields, but they doesn't
}
}
You can use a mix-in, since it has priority over annotations.
#JsonInclude(JsonInclude.Include.ALWAYS)
public class MixIn {
}
And add it to the mapper
ObjectMapper mapper = new ObjectMapper();
mapper.addMixIn(POJO.class, MixIn.class);
The result will be
{"name":null,"description":null}

Categories