Deserialize this Json Object using Jackson? - json

I have been trying to deserialize the data received from this API:
{
"result": "success",
"timestamp": 1521038012878,
"data": {
"GB": 14,
"DE": 2,
"US": 2
},
"totalIsPublic": true,
"advanced": false,
"totalDownloads": {
"GB": 14,
"DE": 2,
"US": 2
}
}
Here is the POJO class:
public class BintrayDownloadCounts {
private List<Integer> totalDownloads = new ArrayList<>();
#JsonProperty("totalDownloads")
public List<Integer> getTotalDownloads() {
return totalDownloads;
}
public void setTotalDownloads(List<Integer> totalDownloads) {
this.totalDownloads = totalDownloads;
}
}
When I tried deserializing using :
downloadCounts = mapper.readValue(json, BintrayDownloadCounts.class);
I get this error:
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.Integer out of START_OBJECT token
I have seen many questions containing this error but I am unable to figure out a solution for this particular use case. It may be noted that the totalDownloads object is dynamic i.e. its contents are not constant.

The totalDownloads property is not an array, so it cannot be mapped to a List<Integer>. Use Map<String, Integer> instead and ensure that you tell Jackson to ignore the properties that are not mapped to avoid mapping errors:
#JsonIgnoreProperties(ignoreUnknown = true)
public class BintrayDownloadCounts {
#JsonProperty("totalDownloads")
private Map<String, Integer> totalDownloads;
public Map<String, Integer> getTotalDownloads() {
return totalDownloads;
}
public void setTotalDownloads(Map<String, Integer> totalDownloads) {
this.totalDownloads = totalDownloads;
}
}
Then you are good to go:
ObjectMapper mapper = new ObjectMapper();
BintrayDownloadCounts downloadCounts = mapper.readValue(json, BintrayDownloadCounts.class);

Related

spring boot json to object mapper with complicated json

I have this list.json that I need to read to mapper object,
{
"name":"first",
"identity":"gold",
"code":{
"csharp":{
"input":"sample of csharp code",
"value":[
{
"main":"true",
"power":"low"
},
{
"main":"false",
"power":"low"
}
],
"description":"description of csharp code",
"manager":"bill gates"
},
"java":{
"input":"sample of java",
"value":[
{
"main":"true",
"power":"low"
},
{
"main":"false",
"power":"high"
},
{
"main":"true",
"power":"low"
}
],
"description":"description of java",
"manager":"steve job"
}
}
},
{
"name":"second",
"identity":"diamond",
"code":{
"python":{
"input":"sample of python code",
"new":"make it more complicated with new parm not value", // do not forget this
"description":"description of python code",
"manager":"john doe"
},
"csharp":{
"input":"sample of csharp code",
"value":[
{
"main":"true",
"power":"low"
},
{
"main":"false",
"power":"low"
}
],
"description":"description of csharp code",
"manager":"bill gates"
},
}
I omit the long list, I only put two base or outter array, but basically its about 200 or more records.
The List.class,
#Data
#AllArgsConstructor
#Entity
public class List {
private String name;
private String identity;
#OneToOne(cascade = CascadeType.ALL)
private Code[] code;
public List() {}
}
Is the Code[] correct and also onetoone or onetomany?
The Code.class,
#Data
#AllArgsConstructor
#Entity
public class Code {
<<I have no idea what to put here>>
}
Do I need to put any string variable for csharp, java, pyhton? They key should be the same as the variable in the class? But how do you do that since it's not constant?
There's a dynamic 2-layer json here in baeldung but how do I do that in the 3-layer?
Here's I got, you have to use JsonNode for the rest of the layers.
I didn't use this annotation for now, don't want to struggle for now, just add getter/setter and constructor using fields, maybe something to do with java 8,
#Data
#AllArgsConstructor
#Entity
#OneToOne(cascade = CascadeType.ALL)
So I remove it. Also how I did it, you have to simulate one by one in the json, meaning I have to add name and identity since those two are similar, if it works, then I add the code as JsonNode.
public class List {
private String name;
private String identity;
JsonNode code;
public List() {}
// put getter/setter
// put constractors as fields
}
Then on your controller,
private String strJson = null;
#PostConstruct
private void loadData() {
ClassPathResource classPathResource = new ClassPathResource("json/list.json");
try {
byte[] binaryData = FileCopyUtils.copyToByteArray(classPathResource.getInputStream());
strJson = new String(binaryData, StandardCharsets.UTF_8);
} catch (IOException e) {
e.printStackTrace();
}
ObjectMapper objectMapper = new ObjectMapper();
DataModel datam = null;
try {
datam = objectMapper.readValue(strJson, List.class);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(datam.code()[0].get("csharp").get("value").get("main"); // output = "true"
Thanks to Baeldung for the idea.

Is there a way to detect unmapped json properties when #JsonIgnoreProperties(ignoreUnknown = true) is set?

I need to detect which json fields are not mapped to the data model after PUT or POST requests.
For example:
If I post this:
{
"firstName": "test",
"lastName": "test 2",
"age": 25
}
and my model only have firstName and lastName, I want to list all unmapped fields, which in this example is "age" field.
Yes, that is possible using Jackson's annotation #JsonAnySetter
#JsonIgnoreProperties(ignoreUnknown = true)
public class DTO {
private String first;
private String last;
private Map<String, Object> unknown = new HashMap<>();
// getters/setters omitted
#JsonAnySetter
public void set(String name, Object value) {
unknown.put(name, value);
}
public Map<String, Object> getUnknown() {
return unknown;
}
}
Simple test:
#Test
public void testUnknown() throws Exception {
String json = "{\"first\":\"John\", \"last\":\"Doe\", \"age\":\"29\"}";
DTO dto = new ObjectMapper().readValue(json, DTO.class);
assertEquals(1, dto.getUnknown().size());
assertEquals("29", dto.getUnknown().get("age"));
}
If it's just about learning which properties are unmapped you may want to consider using this library: https://github.com/whiskeysierra/jackson-module-unknown-property
It logs unmapped properties for all mapped classes without a need to modify class itself.

Custom Json Deserializer for a generic class type

Consider a json of type "Clothing":
{
"id":"123",
"version":2,
"apparel":{
"category":[
{
"id":"a1",
"style":"top",
"comments":[
{
"header":{
"type":"apparel.detail.Summary",
"major_version":1,
"minor_version":0
},
"summary": "notes"
}]
}
]
},
"accessories":[
{
"header":{
"type":"accessories.detail.Handbag",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Gucci",
"sno.":"G12"
},
"color":"Red",
},
{
"header":{
"type":"accessories.detail.Hat",
"major_version":1,
"minor_version":0
},
"details":{
"brand":"Adidas",
"sno.":"A12"
}
}
]
}
"Clothing" is not accessible to me and I cannot add any field level or class level json annotations.
There is a property "header" in json that helps me to determine the type of class I want to convert that entity into. I will remove the header from my json once the class type is determined (since header is not defined in my target class type because of which deserialization will fail)
I need to write a custom deserializer that returns a generic class type object. It will check if there is header, fetch target class name, remove header and deserialize it to the fetched target class and return.
This is the code that I have written, but it does not work and I am not even sure if it is possible to have a custom deserializer injected in SimpleModule with a generic return type.
#Singleton
#Provides
private Transformer provideTransformer(final HeaderDeserializer headerDeserializer) {
final SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(Object.class, headerDeserializer);
mapper.registerModule(simpleModule);
}
#Singleton
#Provides
private HeaderDeserializer provideHeaderDeserializer(final ObjectMapper objectMapper) {
return new HeaderDeserializer(objectMapper);
}
#Singleton
#Provides
private ObjectMapper provideObjectMapper() {
final ObjectMapper mapper = new ObjectMapper()
// Tell object mapper how to handle joda-time.
.registerModule(new JodaModule())
// include non-null values only
.setSerializationInclusion(Include.NON_NULL)
// ensures that timezone is preserved
.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
return mapper;
}
My HeaderDeserializer looks something like this:
public class HeaderDeserializer<T> extends StdDeserializer<T> {
private static final long serialVersionUID = 1L;
private final ObjectMapper mapper;
public HeaderDeserializer(final ObjectMapper mapper) {
this(null, mapper);
}
public HeaderDeserializer(final Class<?> vc, final ObjectMapper mapper) {
super(vc);
this.mapper = mapper;
}
#Override
public T deserialize(final JsonParser jp, final DeserializationContext ctx) {
Object value = null;
try {
JsonNode node = this.mapper.readTree(jp);
JsonNode header = node.get("header");
if (node.has("header")) {
String targetClass = header.get("type").textValue();
removeHeaderFromJsonDoc(node);
value = this.mapper.readValue(jp, Class.forName(targetClass));
}
} catch (final IOException e) {
throw new UncheckedIOException(e);
} catch (final ClassNotFoundException e) {
// do somehting
}
return (T) value;
}
private void removeHeaderFromJsonDoc(final JsonNode document) {
final Iterator<Entry<String, JsonNode>> itr = document.fields();
while (itr.hasNext()) {
final Entry<String, JsonNode> childNodeEntry = itr.next();
if (childNodeEntry.getKey().equals("header")) {
itr.remove();
}
}
}
}
And my main deserializer which will use the custom deserializer defined above looks like:
public final Clothing deserialize(
final String stringValue,
final Class<? extends Clothing> clazz) {
try {
return this.objectMapper.readValue(stringValue, clazz);
} catch (final IOException e) {
throw new IllegalArgumentException();
}
}
this.objectMapper.readValue(stringValue, clazz);
Class type of 'clazz' in this readValue method should match class type passed in simpleModule.addDeserializer.
It is not going inside your deserializer because you are adding deserializer to SimpleModule for 'Object' class and reading value for different class passed to 'Clothing deserialize',

Using javax.ws.rs.core.Response.readEntity to extract a list of strings from a JSON object

If my response is like:
{
"values": [ "1", "2" ]
}
How should I use readEntity to populate a List<String> with the values: 1, 2?
You can read the entity as a Map<String, List<String>>:
Map<String, List<String>> map =
response.readEntity(new GenericType<Map<String, List<String>>>() { });
List<String> values = map.get("values");
Or define your own POJO:
public class MyBean {
private List<String> values;
// Getters and setters
}
List<String> values = response.readEntity(MyBean.class).getValues();
You obviously must have a JSON provider such as Jackson registered.

Create JsonObject from #ResponseBody

#RequestMapping(value = "/dropDown", method = RequestMethod.GET)
public #ResponseBody
DropDown getList(Map<String, Object> map, HttpServletRequest request,
HttpServletResponse response) {
DropDown dropDown = new DropDown();
List<Map<String, Object>> rows = new ArrayList<Map<String, Object>>();
List<MapTable2> list = contactService.mapProcess();
for (MapTable2 table : list) {
Map<String, Object> dataRow = new HashMap<String, Object>(1);
dataRow.put("text", table.getProcess());
dataRow.put("value", table.getId());
dataRow.put("selected", false);
dataRow.put("description", table.getProcess());
dataRow.put("imageSrc", "image.jpg");
rows.add(dataRow);
}
dropDown.setRows(rows);
return dropDown;
}
I need to create following one
var ddData = [
{
text: "Facebook",
value: 1,
selected: false,
description: "Description with Facebook",
imageSrc: "http://dl.dropbox.com/u/40036711/Images/facebook-icon-32.png"
},
{
text: "Twitter",
value: 2,
selected: false,
description: "Description with Twitter",
imageSrc: "http://dl.dropbox.com/u/40036711/Images/twitter-icon-32.png"
}]
I know the issue with my above java coding , I'm not aware to create json array like above.
please check it and help me to correct it.
MapTable2 has ProcessId & ProcessName
public class MapTable2 {
private int id;
private String process;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getProcess() {
return process;
}
public void setProcess(String process) {
this.process = process;
}
}
#theon is right.
Since you are using #Responsebody you can let Spring do the JSON conversion for you. Create a class that matches the objects in the JSON array:
public class SomeObject {
public String getText() { //... }
public int getValue() { //... }
public boolean getSelected { // ... }
public String getDescription { // ... }
public String getImageSrc { // ... }
}
Populate the objects and return it as a list from your controller:
#RequestMapping(value = "/dropDown", method = RequestMethod.GET)
#ResponseBody
public List<SomeObject> getList(Map<String, Object> map, HttpServletRequest request, HttpServletResponse response) {
// Get the objects, return them in a list
}
Add the <mvc:annotation-driven /> or the #EnableWebMvc to your application config unless you have not already done so. Make sure that Jackson is available on your classpath and then Spring will automatically serialize your objects to JSON (if the request has Content-Type: application/json. Alternatively, the produces attribute can be added to the #RequestMapping annotation to always return JSON).
Well Use this library. It is very light weight (16KB) and does exactly what you need.
So in your case, you will be using JSONObject which internally extends HashMap
and do
JSONObject o = new JSONObject();
o.put("text","whatever text");
o.put("value",1);
o.put("selected",false);
//and so on
JSONArray arr = new JSONArray();
arr.add(o);
The above will give you this:
[
{
text: "Facebook",
value: 1,
selected: false,
description: "Description with Facebook",
imageSrc: "http://dl.dropbox.com/u/40036711/Images/facebook-icon-32.png"
}
]
To add more objects, add more JSONObjects in a loop to JSONArray
So as per your given code, just replace dataRow with 'JSONObject' and rows with JSONArray and thats it. In the end, to retrieve the string, do rows.toString().