I have a JSON as below. The goal is to get the corresponding "ip","PRODUCTTYPE" and "ID" values.
{
"customerId": "dummy1",
"nameIdmap": {
"10.2.1.0": "{PRODUCTTYPE=null, ID=123}",
"10.2.1.3": "{PRODUCTTYPE=null, ID=456}",
"10.2.1.4": "{PRODUCTTYPE=null, ID=789}",
"10.2.1.5": "{PRODUCTTYPE=null, ID=193}"
}
}
I am using the ObjectMapper API to parse and fetch the values.
ObjectMapper om = new ObjectMapper();
JsonNode node = om.readTree(stringToBeParsed);
String customerID = node.get("customerId").asText();
System.out.println("The Customer ID is ::: "+customerID);
JsonNode nameIdmap = node.get("nameIdmap");
StreamSupport.stream(nameIdmap.spliterator(), false).forEach(
kv -> {
System.out.println(kv.asText().split(",")[0] +" ,
"+kv.asText().split(",")[1]);
});
But the issue is I, am unable to get the key which is the ip-address in this case. Tried different ways to achieve but could not get what i want.
I checked if the nameIdmap is an array nameIdmap.isArray() but it is false.
I also tried below but could not get the ip i.e the key
JsonNode nameIdmap = node.get("nameIdmap");
StreamSupport.stream(nameIdmap.spliterator(), false).collect(Collectors.toList())
.forEach(item -> {
System.out.println(item.asText());
});;
You can try Custom Deserializer as below
1. Create Item class
This is a POJO which stands for an ID and a map of String and IPItem
public class SOItem {
#Override
public String toString() {
return "SOItem [id=" + id + ", map=" + map + "]";
}
String id;
Map<String, SOIPItem> map = new HashMap();
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Map<String, SOIPItem> getMap() {
return map;
}
public void setMap(Map<String, SOIPItem> map) {
this.map = map;
}
}
2. Create IPItem class
This is a POJO for an ID and ProductType
public class SOIPItem {
private String type;
private String id;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#Override
public String toString() {
return "SOIPItem [type=" + type + ", id=" + id + "]";
}
public SOIPItem(String type, String id) {
super();
this.type = type;
this.id = id;
}
}
3. Create a Custom Deserializer
import java.io.IOException;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class SOCustDeser extends StdDeserializer<SOItem> {
public SOCustDeser() {
this(null);
}
public SOCustDeser(Class<?> vc) {
super(vc);
}
/**
*
*/
private static final long serialVersionUID = -394222274225082713L;
#Override
public SOItem deserialize(JsonParser parser, DeserializationContext arg1)
throws IOException, JsonProcessingException {
SOItem soItem = new SOItem();
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
soItem.setId(node.get("customerId").asText());
JsonNode idmap = node.get("nameIdmap");
Iterator<String> fieldNames = idmap.fieldNames();
while(fieldNames.hasNext()) {
String ip = fieldNames.next();
String textValue = idmap.get(ip).asText();
Pattern p = Pattern.compile("(.*?)=(.*?),(.*?)(\\d+)");
Matcher m = p.matcher(textValue);
if (m.find()) {
soItem.map.put(ip, new SOIPItem(m.group(2), m.group(4)));
}
}
return soItem;
}
}
4. Test class
import java.io.File;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
public class MicsTest {
public static void main(String[] args) throws Exception {
ObjectMapper om = new ObjectMapper();
SimpleModule sm = new SimpleModule();
sm.addDeserializer(SOItem.class, new SOCustDeser());
om.registerModule(sm);
SOItem item = om.readValue(new File("c:\\temp\\test.json"), SOItem.class);
System.out.println(item);
}
}
5. Output
SOItem [id=dummy1, map={10.2.1.0=SOIPItem [type=null, id=123], 10.2.1.3=SOIPItem [type=null, id=456], 10.2.1.5=SOIPItem [type=null, id=193], 10.2.1.4=SOIPItem [type=null, id=789]}]
You can get the field names by nameIdmap.getFieldNames as an iterator. You can then iterate over like that:
...
Iterator<String> fieldNames = idmap.getFieldNames();
while(fieldNames.hasNext()) {
String ip = fieldNames.next();
String textValue = idmap.get(ip).getTextValue()
System.out.println(ip + ":" + textValue);
}
If the nested information is also JSON you can then access it further via idmap.get(ip).get("ID"); if not then you still have the option to find it by regex like that:
Pattern p = Pattern.compile("ID=(\\d+)");
Matcher m = p.matcher(textValue);
if(m.find()) {
System.out.println(ip + ":" + m.group(1));
}
Best way to handle these scenarios is to create a matching pojo for your json. This way it gives you flexibility to play around with the data.
Create classes like these
public class Someclass {
private String customerId;
Map<String, String> nameIdmap;
public Map<String, String> getNameIdmap() {
return nameIdmap;
}
public void setNameIdmap(Map<String, String> nameIdmap) {
this.nameIdmap = nameIdmap;
}
public Someclass() {
}
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
}
And this code will translate your json to SomeClass class
String json = "<copy paste your json here>";
Someclass someclass = objectMapper.readValue(json, Someclass.class);
String s = someclass.getNameIdmap().get("10.2.1.0");
String[] splits = s.split(" ");
String productType = splits[0].split("=")[1];
String id = splits[1].split("=")[1];
System.out.println(productType + " " + id);
Related
I have a restApi which its response can be something like this (if there is only one object to return):
{
"keys":
{
"id":0,
"name":"john",
"pItems":12
}
}
or like this if there is more:
{
"keys":
[
{
"id":0,
"name":"john",
"pItems":12
},
{
"id":0,
"name":"john",
"pItems":12
}
]
}
When I use a list for Model object, the first case doesn't work.
How can I deserialize it using Gson and Retrofit2?
Ok you cannot change the design of the response, but are you sure you only can receive those responses? if is like this maybe you can create something like this:
class Response{
public Object keys
}
class UserResponse{
public int id;
public String name;
public int pItems;
}
So you going to have two cases:
you can receive an UserResponse object
you can receive a List of UserResponse object
Then to validate if is a List of UserResponse, can be something like this:
if (keys instanceof List<?>){
// then keys is a list
}else{
// then keys is a single object UserResponse
}
You can check if keys element is array or not and then deserialize accordingly.
Assuming, your Model class is UserWrapper.java
import java.util.List;
public class UserWrapper {
private List<User> keys;
public List<User> getKeys() {
return keys;
}
public void setKeys(List<User> keys) {
this.keys = keys;
}
}
User.java is class corresponding to each element of keys attribute.
public class User {
private int id;
private String name;
private int pitems;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPitems() {
return pitems;
}
public void setPitems(int pitems) {
this.pitems = pitems;
}
}
Code to deserialize based on type of keys
import java.util.ArrayList;
import java.util.List;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
public class GsonMain{
private static String json = "YOUR JSON";
public static void main(String[] args) {
Gson gson = new GsonBuilder().create();
JsonElement jsonElement = gson.fromJson(json, JsonElement.class);
JsonElement keyselement = jsonElement.getAsJsonObject().get("keys");
UserWrapper userWrapper = new UserWrapper();
if (keyselement.isJsonObject()) {
userWrapper.setKeys(new ArrayList<User>());
User user = gson.fromJson(keyselement, User.class);
userWrapper.getKeys().add(user);
} else if (keyselement.isJsonArray()) {
List<User> users = gson.fromJson(keyselement, new TypeToken<List<User>>() {
}.getType());
userWrapper.setKeys(users);
}
userWrapper.getKeys().forEach(user -> System.out.println(user.getName()));
}
}
I have 2 API responses and converted them into Json Array. When I convert 2 jsons into a map of key value pair the order of values are different and unable to perform comparison between 2 API responses.
JsonArray from API 1:
[
{
"employeeSalutation": null,
"EmployeeName": "Example",
"EmployeeCode": "SAA",
"Zip": 12345,
"DepartmentName": "Social science",
"EmployeeAddress": "123 st",
"StateCode": 9,
"updatedDate": "2018-01-22T03:48:43.59",
"EmployeeId": 1257
}
]
Json Array from API 2:
[
{
"Emp_Name": "Example",
"emp_Sal": null,
"Dept_Name": "Social science",
"Emp_addr": "123 st",
"Zip": "12345",
"Stat_cd": 9,
"Emp_id": 1257,
"upd_d": "2018-01-22 03:48:43.59",
"Emp_Code": "SAA"
}
]
I converted 2 Json Arrays into map of key-value pair with EmployeeId as key in Array 1 and Emp_id as key in Array 2. When I compare 2 maps the order of values in map are different and getting failed.
How to compare 2 APIs to ensure the values of each element in 2 apis are matched.
First of all you need to create a model which will represent these two JSON payloads. They are almost similar except of key names and values for Zip key where in first payload it is a number primitive in second - String primitive. Dates also have different format so you need to handle them by implementing custom date deserialiser.
Definitely you should use Jackson or Gson library which allows to deserialise JSON to POJO model, provide custom date deserialisers, any many other features.
Below example provide example solution:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import java.io.File;
import java.io.FileReader;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Objects;
public class GsonApp {
public static void main(String[] args) throws Exception {
File jsonApi1 = new File("./resource/test.json").getAbsoluteFile();
File jsonApi2 = new File("./resource/test1.json").getAbsoluteFile();
Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new JsonDeserializer<Date>() {
private final SimpleDateFormat formatWithTimeZoneIndicator = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SS");
private final SimpleDateFormat formatWithoutTimeZoneIndicator = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SS");
#Override
public Date deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
String date = json.getAsString();
try {
return formatWithoutTimeZoneIndicator.parse(date);
} catch (ParseException e) {
try {
return formatWithTimeZoneIndicator.parse(date);
} catch (ParseException e1) {
throw new JsonParseException(e1);
}
}
}
})
.create();
Type employeesType = new TypeToken<List<Employee>>() {}.getType();
try (FileReader readerApi1 = new FileReader(jsonApi1);
FileReader readerApi2 = new FileReader(jsonApi2)) {
List<Employee> employees1 = gson.fromJson(readerApi1, employeesType);
List<Employee> employees2 = gson.fromJson(readerApi2, employeesType);
System.out.println(employees1);
System.out.println(employees2);
System.out.println(employees1.equals(employees2));
}
}
}
class Employee {
#SerializedName(value = "employeeSalutation", alternate = {"emp_Sal"})
private String employeeSalutation;
#SerializedName(value = "EmployeeName", alternate = {"Emp_Name"})
private String employeeName;
#SerializedName(value = "EmployeeCode", alternate = {"Emp_Code"})
private String employeeCode;
#SerializedName("Zip")
private JsonPrimitive zip;
#SerializedName(value = "DepartmentName", alternate = {"Dept_Name"})
private String departmentName;
#SerializedName(value = "EmployeeAddress", alternate = {"Emp_addr"})
private String employeeAddress;
#SerializedName(value = "StateCode", alternate = {"Stat_cd"})
private int stateCode;
#SerializedName(value = "updatedDate", alternate = {"upd_d"})
private Date updatedDate;
#SerializedName(value = "EmployeeId", alternate = {"Emp_id"})
private int employeeId;
public String getEmployeeSalutation() {
return employeeSalutation;
}
public void setEmployeeSalutation(String employeeSalutation) {
this.employeeSalutation = employeeSalutation;
}
public String getEmployeeName() {
return employeeName;
}
public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}
public String getEmployeeCode() {
return employeeCode;
}
public void setEmployeeCode(String employeeCode) {
this.employeeCode = employeeCode;
}
public JsonPrimitive getZip() {
return zip;
}
public void setZip(JsonPrimitive zip) {
this.zip = zip;
}
public String getDepartmentName() {
return departmentName;
}
public void setDepartmentName(String departmentName) {
this.departmentName = departmentName;
}
public String getEmployeeAddress() {
return employeeAddress;
}
public void setEmployeeAddress(String employeeAddress) {
this.employeeAddress = employeeAddress;
}
public int getStateCode() {
return stateCode;
}
public void setStateCode(int stateCode) {
this.stateCode = stateCode;
}
public Date getUpdatedDate() {
return updatedDate;
}
public void setUpdatedDate(Date updatedDate) {
this.updatedDate = updatedDate;
}
public int getEmployeeId() {
return employeeId;
}
public void setEmployeeId(int employeeId) {
this.employeeId = employeeId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Employee employee = (Employee) o;
return stateCode == employee.stateCode &&
employeeId == employee.employeeId &&
Objects.equals(employeeSalutation, employee.employeeSalutation) &&
Objects.equals(employeeName, employee.employeeName) &&
Objects.equals(employeeCode, employee.employeeCode) &&
Objects.equals(zip.getAsString(), employee.zip.getAsString()) &&
Objects.equals(departmentName, employee.departmentName) &&
Objects.equals(employeeAddress, employee.employeeAddress) &&
Objects.equals(updatedDate, employee.updatedDate);
}
#Override
public int hashCode() {
return Objects.hash(employeeSalutation, employeeName, employeeCode, zip, departmentName, employeeAddress, stateCode, updatedDate, employeeId);
}
#Override
public String toString() {
return "Employee{" +
"employeeSalutation='" + employeeSalutation + '\'' +
", employeeName='" + employeeName + '\'' +
", employeeCode='" + employeeCode + '\'' +
", zip=" + zip +
", departmentName='" + departmentName + '\'' +
", employeeAddress='" + employeeAddress + '\'' +
", stateCode=" + stateCode +
", updatedDate='" + updatedDate + '\'' +
", employeeId=" + employeeId +
'}';
}
}
Above code prints:
[Employee{employeeSalutation='null', employeeName='Example', employeeCode='SAA', zip=12345, departmentName='Social science', employeeAddress='123 st', stateCode=9, updatedDate='Mon Jan 22 03:48:43 CET 2018', employeeId=1257}]
[Employee{employeeSalutation='null', employeeName='Example', employeeCode='SAA', zip="12345", departmentName='Social science', employeeAddress='123 st', stateCode=9, updatedDate='Mon Jan 22 03:48:43 CET 2018', employeeId=1257}]
true
JSONAssert librairy is very handy for such tasks.
Here is good tutorial on how to use it.
I hope it helps you.
Since you are doing this in Java, I suggest you map this values into Java objects.
That way, it would be extremely easy for you to compare values. You can use libraries like Gson for that but here i will show you how to do that without a library.
Create a class which will be easy to use for your API responses:
public class Employee {
private String employeeSalutation; //i am not sure what type should this variable be
private String employeeName;
private String employeeCode;
private int zip;
private String departmentName;
private String employeeAddress;
private int stateCode;
private String updatedDate;
private int employeeId;
//this is where your parser can be used
public Employee(String employeeSalutation, String employeeName, String employeeCode,
int zip, String departmentName, String employeeAddress, int stateCode,String updatedDate, int employeeId){
this.employeeSalutation = employeeSalutation;
this.employeeName = employeeName;
this.employeeCode = employeeCode;
this.zip = zip;
this.departmentName = departmentName;
this.employeeAddress = employeeAddress;
this.stateCode = stateCode;
this.updatedDate = updatedDate;
this.employeeId = employeeId;
}
//getters, setters
}
Now you could have a class which will be used to convert this arrays into Java Objects, lets call it EmployeeConverter:
public class EmployeeConverter {
public Employee convertApi1Employee(String json){
//since you didn't clarify how are you accessing your Json values, i will now use pseudo-code to finish my answer.
//using variable.value pseudo-code to access json property value
return new Employee(employeeSalutation.value, EmployeeName.value, EmployeeCode.value, Zip.value, DepartmentName.value, EmployeeAddress.value, EmployeeAddress.value, StateCode.value, updatedDate.value, EmployeeId.value);
}
}
You should create method convertApi2Employee() which will have exact functionality as convertApi1Employee() method, but will use different JSON object.
I hope this helps.
I'm trying to deserialize some xml with nested properties with the same name, but the wrapper name is unique for each property. Example XML below.
I've tried playing with switching wrapper and property names but doesn't seem to work.
<response>
<string>
<item>Sample string.</item>
<item>Another sample string.</item>
</string>
<number>
<item>123123123</item>
<item>900912</item>
</number>
</response>
I'm trying to deserialize the above XML into a List<String> and List<Integer> variable.
I managed to make it creating a pair or wrappers of ArrayLists as inner classes:
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
#JacksonXmlRootElement(localName="response")
public class ResponseObjectList implements Serializable {
#JacksonXmlProperty(localName = "string")
private StringArrayListContainer string;
#JacksonXmlProperty(localName = "number")
private IntegerArrayListContainer number;
public ResponseObjectList() {
super();
}
public ResponseObjectList(List<String> stringItems, List<Integer> intItems) {
super();
this.string = new StringArrayListContainer(stringItems);
this.number = new IntegerArrayListContainer(intItems);
}
public StringArrayListContainer getString() {
return string;
}
public void setString(StringArrayListContainer string) {
this.string = string;
}
public IntegerArrayListContainer getNumber() {
return number;
}
public void setNumber(IntegerArrayListContainer number) {
this.number = number;
}
public static class StringArrayListContainer extends ArrayListContainer<String>{
public StringArrayListContainer() {
super();
}
public StringArrayListContainer(List<String> item) {
super(item);
}
}
public static class IntegerArrayListContainer extends ArrayListContainer<Integer>{
public IntegerArrayListContainer() {
super();
}
public IntegerArrayListContainer(List<Integer> item) {
super(item);
}
}
public static class ArrayListContainer<T extends Serializable>{
#JacksonXmlElementWrapper(useWrapping=false)
#JacksonXmlProperty(localName="item")
private List<T> item;
public ArrayListContainer(List<T> item) {
super();
this.item = item;
}
public ArrayListContainer() {
super();
}
public List<T> getItem() {
return item;
}
public void setItem(List<T> item) {
this.item = item;
}
}
}
Tests looked good:
#Test
public void test3() throws JsonProcessingException {
ResponseObjectList response = new ResponseObjectList(
Arrays.asList(new String[] {"Sample string.","Another sample string"}),
Arrays.asList(new Integer[] {123123123,900912})
);
XmlMapper xmlMapper = new XmlMapper();
String content = xmlMapper.writeValueAsString(response);
this.logger.debug("content: " + content);
// content: <response xmlns=""><string><item>Sample string.</item><item>Another sample string</item></string><number><item>123123123</item><item>900912</item></number></response>
}
#Test
public void test4() throws JsonParseException, JsonMappingException, IOException {
String xml =
"<response>"
+ "<string>"
+ "<item>Sample string.</item>"
+ "<item>Another sample string</item>"
+ "</string>"
+ "<number>"
+ "<item>123123123</item>"
+ "<item>900912</item>"
+ "</number>"
+ "</response>";
XmlMapper xmlMapper = new XmlMapper();
ResponseObjectList object = xmlMapper.readValue(xml, ResponseObjectList.class);
Assert.assertFalse(object.getString().getItem().isEmpty());
Assert.assertFalse(object.getNumber().getItem().isEmpty());
}
I used Lists instead of ArrayLists both for the wrappers and the tests
For version 2.9.9 simple POJO with JacksonXmlElementWrapper annotation works as expected:
class Response {
#JacksonXmlElementWrapper(localName = "string")
private List<String> strings;
#JacksonXmlElementWrapper(localName = "number")
private List<Integer> numbers;
// getters, setters
}
I have a class like this
public class Item {
#XmlElement(name = "my_id")
private String id;
#XmlElement(name = "my_type")
private String type;
}
I would like to convert this class to a Map which considers the jaxb annotated fields.
E.g. the Result is a map with following entries:
Key: my_id , Value: "the id"
Key: my_type , Value: "the type"
I am not sure how your xml looks like but assuming, it will be an item element, under some parent, you can do it usingadapter (XmlJavaTypeAdapter) . Sample code looks like below:
package test;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
#XmlAccessorType(XmlAccessType.FIELD)
public class Item {
public Item() {
}
#XmlElement(name = "my_id")
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#XmlElement(name = "my_type")
private String type;
public String toString() {
return "Item : id-" + getId() + ", type -" + getType();
}
public static void main(String[] args) throws Exception {
String xmlString = "<items>\n"
+ " <item>\n"
+ " <my_id>someID</my_id>\n"
+ " <my_type>someType</my_type>\n"
+ " </item>\n"
+ "</items>";
//System.out.println("xmlString.." + xmlString);
RootElement o = unmarshal(RootElement.class, xmlString);
System.out.println("item Map : "+o.getItem());
}
private static <C> C unmarshal(Class<C> c, String sampleXML) throws Exception {
JAXBContext jc = JAXBContext.newInstance(c);
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader reader = new StringReader(sampleXML);
//System.out.println("" + sampleXML);
return (C) unmarshaller.unmarshal(reader);
}
}
#XmlRootElement(name = "items")
#XmlAccessorType(XmlAccessType.FIELD)
class RootElement {
public RootElement() {
System.out.println("RootElement");
}
public Map<String, String> getItem() {
return item;
}
public void setItem(Map<String, String> item) {
this.item = item;
}
#XmlJavaTypeAdapter(ItemAdapter.class)
#XmlElement()
private Map<String, String> item;
}
class ItemAdapter extends XmlAdapter<Item, Map<String, String>> {
#Override
public Map<String, String> unmarshal(Item i) throws Exception {
Map<String, String> r = new HashMap<String, String>();
r.put("my_id", i.getId());
r.put("my_type", i.getType());
return r;
}
#Override
public Item marshal(Map<String, String> v) throws Exception {
Item i = new Item();
i.setId(v.get("my_id"));
i.setType(v.get("my_type"));
return i;
}
}
I want to create a common dto like as shown below for receiving the incoming Manager and Staff details from a REST service
public class Employee {
#JsonProperty("name")
public String name;
#JsonProperty("designation")
public String designation;
#JsonProperty("item")
public String item;
#JsonProperty("item")
public List<Item> items;
//setters and getters
}
The problem is that for for Manager the item field will be a List where as for Staff it will be a string, so I have created two field for item, one for receiving String and another for List, but its not working and I am getting Can not deserialize instance of java.util.ArrayList out of VALUE_STRING token.
The incoming json details is like as shown below
Manager incoming json
{
"name": "Rohit",
"designation": "Manager",
"item": {"name": "ABC", "desc": "1234"}
}
Staff incoming json
{
"name": "Manu",
"designation": "Staff",
"item": "abc"
}
Can anyone please tell me some solution for this
You could create a customer deserializer. If the node for the "item" field is an array, then deserialize it as an array, otherwise deserialize it as a String. For example
public static class EmployeeDeserializer extends JsonDeserializer<Employee> {
#Override
public Employee deserialize(JsonParser jp,
DeserializationContext dc)
throws IOException, JsonProcessingException {
Employee emp = new Employee();
JsonNode root = jp.getCodec().readTree(jp);
emp.name = root.get("name").asText();
emp.designation = root.get("designation").asText();
JsonNode itemNode = root.get("item");
if (itemNode.isArray()) {
ArrayNode itemsNode = (ArrayNode) itemNode;
List<Item> items = new ArrayList<>();
for (JsonNode iNode : itemsNode) {
Item item = new Item();
item.name = iNode.get("name").asText();
item.desc = iNode.get("desc").asText();
items.add(item);
}
emp.items = items;
} else if (itemNode.isObject()) {
List<Item> items = new ArrayList<>();
Item item = new Item();
item.name = itemNode.get("name").asText();
item.desc = itemNode.get("desc").asText();
items.add(item);
emp.items = items;
} else {
String item = root.get("item").asText();
emp.item = item;
}
return emp;
}
}
I actually added three cases for "item". It could be a JSON array as multiple Items, a JSON object (which is what you have in your post) as a single Item, or a String for the staff. If it's a JSON object, I just create the single Item and added it to a List
Here is a complete test
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
public class EmployeeTest {
#JsonDeserialize(using = EmployeeDeserializer.class)
public static class Employee {
public String name;
public String designation;
public String item;
public List<Item> items;
}
public static class Item {
public String name;
public String desc;
#Override
public String toString() {
return "Item{" + "name=" + name + ", desc=" + desc + '}';
}
}
public static class EmployeeDeserializer extends JsonDeserializer<Employee> {
#Override
public Employee deserialize(JsonParser jp,
DeserializationContext dc)
throws IOException, JsonProcessingException {
Employee emp = new Employee();
JsonNode root = jp.getCodec().readTree(jp);
emp.name = root.get("name").asText();
emp.designation = root.get("designation").asText();
JsonNode itemNode = root.get("item");
if (itemNode.isArray()) {
ArrayNode itemsNode = (ArrayNode) itemNode;
List<Item> items = new ArrayList<>();
for (JsonNode iNode : itemsNode) {
Item item = new Item();
item.name = iNode.get("name").asText();
item.desc = iNode.get("desc").asText();
items.add(item);
}
emp.items = items;
} else if (itemNode.isObject()) {
List<Item> items = new ArrayList<>();
Item item = new Item();
item.name = itemNode.get("name").asText();
item.desc = itemNode.get("desc").asText();
items.add(item);
emp.items = items;
} else {
String item = root.get("item").asText();
emp.item = item;
}
return emp;
}
}
private static ObjectMapper mapper;
#BeforeClass
public static void setUpMapper() {
mapper = new ObjectMapper();
//SimpleModule module = new SimpleModule();
//module.addDeserializer(Employee.class, new EmployeeDeserializer());
//mapper.registerModule(module);
}
#Test
public void should_deserialize_manager_list_ok() throws Exception {
final String mgrJson
= "{\n"
+ " \"name\": \"Rohit\",\n"
+ " \"designation\": \"Manager\",\n"
+ " \"item\": [{\"name\": \"ABC\", \"desc\": \"1234\"}]\n"
+ "}";
Employee mgr = mapper.readValue(mgrJson, Employee.class);
assertEquals("Rohit", mgr.name);
assertEquals("Manager", mgr.designation);
assertNull(mgr.item);
assertEquals(1, mgr.items.size());
assertEquals("ABC", mgr.items.get(0).name);
assertEquals("1234", mgr.items.get(0).desc);
}
#Test
public void should_deserialize_staff_string_ok() throws Exception {
final String staffJson
= "{\n"
+ " \"name\": \"Manu\",\n"
+ " \"designation\": \"Staff\",\n"
+ " \"item\": \"abc\"\n"
+ "}";
Employee staff = mapper.readValue(staffJson, Employee.class);
assertEquals("Manu", staff.name);
assertEquals("Staff", staff.designation);
assertEquals("abc", staff.item);
assertNull(staff.items);
}
#Test
public void should_deserialize_single_item_ok() throws Exception {
final String mgrJson
= "{\n"
+ " \"name\": \"Rohit\",\n"
+ " \"designation\": \"Manager\",\n"
+ " \"item\": {\"name\": \"ABC\", \"desc\": \"1234\"}\n"
+ "}";
Employee mgr = mapper.readValue(mgrJson, Employee.class);
assertEquals("Rohit", mgr.name);
assertEquals("Manager", mgr.designation);
assertNull(mgr.item);
assertEquals(1, mgr.items.size());
assertEquals("ABC", mgr.items.get(0).name);
assertEquals("1234", mgr.items.get(0).desc);
}
}
You can make Manager and Staff extend an abstract class Employee, and then use the 'designation' JSON property to distinguish them:
// note hashCode(), equals() and toString() methods left out for brevity!
#JsonTypeInfo(use = Id.NAME, include = As.PROPERTY, property = "designation")
#JsonSubTypes({ #JsonSubTypes.Type(Staff.class), #JsonSubTypes.Type(Manager.class) })
public static abstract class Employee {
#JsonProperty("name")
public String name;
public Employee(String name) {
this.name = name;
}
public Employee() {
}
}
#JsonTypeName("Manager")
public static final class Manager extends Employee {
#JsonProperty("item")
public List<Item> items;
public Manager(String name, List<Item> items) {
super(name);
this.items = items;
}
public Manager() {
}
public static final class Item {
public final String name;
public final String desc;
public Item(#JsonProperty("name") String name, #JsonProperty("desc") String desc) {
this.name = name;
this.desc = desc;
}
}
}
#JsonTypeName("Staff")
public static final class Staff extends Employee {
#JsonProperty("item")
public String item;
public Staff(String name) {
super(name);
this.item = item;
}
public Staff() {
}
}
Tests:
#Test
public void polymorphic_deserialization_of_manager() throws Exception {
ObjectMapper mapper = new ObjectMapper().enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES)
.enable(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES)
.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.enable(SerializationFeature.WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED);
String json = "{ name: 'Rohit', designation: 'Manager', item: { name: 'ABC', desc: '1234' } }";
Employee employee = new Manager("Rohit", ImmutableList.of(new Manager.Item("ABC", "1234")));
assertThat(mapper.readValue(json, Employee.class), equalTo(employee));
assertThat(mapper.writeValueAsString(employee), equivalentTo(json));
}
#Test
public void polymorphic_deserialization_of_staff() throws Exception {
ObjectMapper mapper = new ObjectMapper().enable(JsonParser.Feature.ALLOW_SINGLE_QUOTES).enable(
JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES);
String json = "{ name: 'Manu', designation: 'Staff', item: 'abc' }";
Employee employee = new Staff("Manu", "abc");
assertThat(mapper.readValue(json, Employee.class), equalTo(employee));
assertThat(mapper.writeValueAsString(employee), equivalentTo(json));
}