How can I use Jackson to parse JSON with variable object names? - java

Google's cAdvisor API gives JSON output like this:
{
/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope: {},
/system.slice/docker-747f797d19931b4ef33cda0c519f935b592a0b828d16b8cafc350568ab2c1d28.scope: {},
/system.slice/docker-bf947bfabf61cd5168bd599162cf5f5c2ea2350eece1ded018faebf598f7ee5b.scope: {},
/system.slice/docker-e8e02d508400438603151dd462ef036d59fada8239f66be8e64813880b59a77d.scope: {
name: "/system.slice/docker-e8e02d508400438603151dd462ef036d59fada8239f66be8e64813880b59a77d.scope",
aliases: [...],
namespace: "docker",
spec: {...},
stats: [...]
}
}
I would describe this as 4 same-typed JSON objects with variable/anonymous names, held in an anonymous object.
My first thought would just do something like mapper.readValue(response, Containers.class), where:
public class Containers extends BaseJsonObject {
#JsonProperty
public List<Container> containerList;
}
and
public class Container extends BaseJsonObject {
#JsonProperty
private String name;
#JsonProperty
public String[] aliases;
#JsonProperty
private String namespace;
#JsonProperty
private String spec;
#JsonProperty
public Stats[] stats;
}
But all of the variations on this I can think of yield the same result: some permutation of com.xyz.Containers#45c7e403[containerList=<null>] or com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope" (class com.xyz.Containers), not marked as ignorable (one known property: "containerList"])
at [Source: java.io.StringReader#3d285d7e; line: 1, column: 97] (through reference chain: com.xyz.Containers["/system.slice/docker-13b18253fa70d837e9707a1c28e45a3573e82751f964b66d7c4cbc2256abc266.scope"]), with ACCEPT_SINGLE_VALUE_AS_ARRAY = false .
I've tried:
mapper.readValue(response, Container[].class)
mapper.readValue(response, Containers.class)
mapper.readValues(jsonParser, Container.class)
As well as the following configurations:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
How can I parse JSON objects with variable/anonymous names, held in a non-array? What is this called?

You can use the #JsonAnySetter annotation as follows and put the objects with variable names into a map of Map<String, Container>.
Here is an example:
public class JacksonVariableNames {
static final String JSON = "{\n"
+ " \"a\": {\n"
+ " \"value\": \"1\"\n"
+ " },\n"
+ " \"b\": {\n"
+ " \"value\": \"2\"\n"
+ " },\n"
+ " \"c\": {\n"
+ " \"value\": \"3\"\n"
+ " }\n"
+ "}";
static class Value {
private final String value;
#JsonCreator
Value(#JsonProperty("value") final String value) {this.value = value;}
#Override
public String toString() {
return "Value{" +
"value='" + value + '\'' +
'}';
}
}
static class Values {
private final Map<String, Value> values = new HashMap<>();
#JsonAnySetter
public void setValue(final String property, final Value value) {
values.put(property, value);
}
#Override
public String toString() {
return "Values{" +
"values=" + values +
'}';
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, Values.class));
}
}
Output:
Values{values={a=Value{value='1'}, b=Value{value='2'}, c=Value{value='3'}}}

Related

Response with lowercase letters in serialization and deserialization Jackson

The response of a WS is a json with first capitalize letters. I'm trying to encapsulate the response in a new MyResponse obj having lowercase first letters.
I'm using Jackson.
At first I have my models:
public class Telephone {
private String country;
private String prefix;
//getters and setters
}
public class Position {
private String x;
private String y;
//getters and setters
}
public class Root {
#JsonProperty("Telephone")
private List<Telephone> telephone;
#JsonProperty("Position")
private List<Position> position;
//getters and setters
}
public class MyResponse {
private final Root root;
private final String now;
public MyResponse(Root root, String now) {
this.root = root;
this.now = now;
}
//getters
}
As you can see above, I used #JsonProperty in my Root class because I want to map my response using a first lowercase letter.
Now I have my RestController:
#Controller
public class RestController {
#GetMapping("/my-controller")
ResponseEntity<String> myController() {
//Simulating the request to my ws to get my json string
String jsonString = "{\n" +
" \"Telephone\":[\n" +
" {\n" +
" \"country\":\"ES\",\n" +
" \"prefix\":\"+34\"\n" +
" },\n" +
" {\n" +
" \"country\":\"FR\",\n" +
" \"prefix\":\"+33\"\n" +
" },\n" +
" {\n" +
" \"country\":\"EN\",\n" +
" \"prefix\":\"+44\"\n" +
" }\n" +
" ],\n" +
" \"Position\":[\n" +
" {\n" +
" \"x\":\"123.23\",\n" +
" \"y\":\"98.93\"\n" +
" },\n" +
" {\n" +
" \"x\":\"250.99\",\n" +
" \"y\":\"43.89\"\n" +
" }\n" +
" ]\n" +
"}";
ObjectMapper om = new ObjectMapper();
Root root = null;
try {
root = om.readValue(jsonString, Root.class);
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
As you can see in the snippet of code above, at the beginning I got the json string (in my real code calling the WS) and I deserialized it into a Java POJO using the readValue method:
root = om.readValue(jsonString, Root.class);
Then I created my MyResponse obj using the deserialized POJO:
MyResponse myResponse = new MyResponse(root, LocalDateTime.now().toString());
And at the end, I serialized myResponse obj to String using om.writeValueAsString and I returned it to my frontend:
String responseAsString = om.writeValueAsString(myResponse);
return new ResponseEntity<>(responseAsString, HttpStatus.OK);
Since MyResponse obj is serialized and deserialized both using my Root #JsonProperty (s)
I get:
{
"root":{
"Telephone":[
{
"country":"ES",
"prefix":"+34"
},
{
"country":"FR",
"prefix":"+33"
},
{
"country":"EN",
"prefix":"+44"
}
],
"Position":[
{
"x":"123.23",
"y":"98.93"
},
{
"x":"250.99",
"y":"43.89"
}
]
},
"now":"2021-06-24T11:18:04.077612"
}
That is not what I am trying to do: I have capitalize letters in my response.
How can I solve this problem? Should I use two different classes for serialization and deserialization?
You can specify your Jackson annotations on getters and setters as well to let them behave differently. Like this:
public class Root {
private List<Telephone> telephone;
#JsonProperty("Telephone")
private void setTelephone(String telephone) {
this.telephone = telephone;
}
#JsonProperty("telephone")
private String getTelephone() {
this.telephone = telephone;
}
}
I've tried what suggested by Felix. But it didn't work, I got the following error:
Conflicting/ambiguous property name definitions found multiple explicit names: but also implicit accessor
After a while I was able to solve my problem in this way:
public class Root {
private List<Telephone> telephone;
private List<Position> position;
#JsonCreator
public Root(#JsonProperty("Telephone") List<Telephone> telephon, #JsonProperty("Position") List<Position> position) {
this.telephon = telephon;
this.position = position;
}
//getters and setters
}
The annotation #JsonCreator is used in deserialization phase only.
When one deserializes Jackson uses the constructor annotated with #JsonCreator. In serialization phase Jackson uses the fields name to convert the obj into String.

UnrecognizedPropertyException from Jackson library

I have JSON paylaod as below:
{
"status": "ok",
"result": {
"result": [
{
"product_id": "1420-131617-82",
"sku": "1420-131617",
"display_sku": "8DD 355 100-411",
"genart_number": 82,
"name": "Bremsscheibe",
"description": null,
"additional_info_text": null,
"product_url": "https://www.autoteile5000.de/product/1420-131617-82",
"image_url": "https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg",
"brand": "HELLA PAGID",
"eans": [
"4082300365078"
],
"manufacturer_product_number": "8DD 355 100-411",
"data_supplier_number": "4625",
"pricehammer": false,
"buyable": true,
"bulky_good": false,
"risky_good": false,
"hazardous_good": false,
"car_specific": true,
"has_deposit": false,
"is_exchange_part": false,
"visibility_status": "active",
"deleted": false
}
]
}
}
This is method how I deserialise it:
public List<SimpleProductDto> getProducts(ProductForm productForm) {
JsonParser jsonParser = new JsonParser();
try (InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("data/product/2210-0929-818/product.json") ) {
String text = IOUtils.toString(inputStream, StandardCharsets.UTF_8.name());
//System.out.println("print text : " + text);
//Read JSON file
JsonObject objectFromString = jsonParser.parse(text).getAsJsonObject();
// System.out.println(objectFromString.toString());
JsonObject objectFromString1 = objectFromString.getAsJsonObject("result");
// System.out.println(objectFromString.toString());
// System.out.println(objectFromString1.toString());
JsonArray jsonArray = objectFromString1.getAsJsonArray("result");
System.out.println("printing json array : " +jsonArray.toString());
ObjectMapper oMapper = new ObjectMapper();
for(JsonElement element : jsonArray){
JsonObject productObj = element.getAsJsonObject();
System.out.println("printing json object : " + productObj.toString());
SimpleproductDtoMapper productDtoList = oMapper.readValue(productObj.toString(), SimpleproductDtoMapper.class);
}
// List<SimpleproductDtoMapper> productDtoList = oMapper.readValue(jsonArray.toString(), new TypeReference<List<SimpleproductDtoMapper>>() {});
// Map<String, SimpleproductDtoMapper> items = productDtoList.stream().collect(Collectors.toMap(SimpleproductDtoMapper::getProductId, Function.identity()));
//items.forEach((k, v) -> System.out.println("Item : " + k + " Count : " + v));
//Iterate over employee array
//productList.forEach(emp -> parseProductObject((JSONObject) emp));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
This is my POJO:
public class SimpleproductDtoMapper {
#SerializedName("product_id")
private String productId;
private String sku;
#SerializedName("display_sku")
private String displaySku;
#SerializedName("genart_number")
private Integer genartNumber;
private String name;
private String description;
#SerializedName("additional_info_text")
private String additionalInfoText;
#SerializedName("product_url")
private String productUrl;
#SerializedName("image_url")
private String imageUrl;
private String brand;
private List<String> eans;
#SerializedName("manufacturer_product_number")
private String manufacturerProductNumber;
#SerializedName("data_supplier_number")
private String dataSupplierNumber;
private boolean pricehammer;
private boolean buyable;
#SerializedName("bulky_good")
private boolean bulkyGood;
#SerializedName("risky_good")
private boolean riskyGood;
#SerializedName("hazardous_good")
private boolean hazardousGood;
#SerializedName("car_specific")
private boolean carSpecific;
#SerializedName("has_deposit")
private boolean hasDeposit;
#SerializedName("is_exchange_part")
private boolean isExchangePart;
#SerializedName("visibility_status")
private VisibilityStatusDto visibilityStatus;
#SerializedName("deleted")
private boolean deleted;
}
I get the folllowing exception:
at [Source:
(String)"{"product_id":"1420-131617-82","sku":"1420-131617","display_sku":"8DD
355
100-411","genart_number":82,"name":"Bremsscheibe","description":null,"additional_info_text":null,"product_url":"https://www.autoteile5000.de/product/1420-131617-82","image_url":"https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg","brand":"HELLA
PAGID","eans":["4082300365078"],"manufacturer_product_number":"8DD 355
100-411","data_supplier_number":"4625","pricehammer":false,"buyable":tr"[truncated
174 chars]; line: 1, column: 16] (through reference chain:
com.kfz24.mockingservice.mapper.SimpleproductDtoMapper["product_id"])
at
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException.from(UnrecognizedPropertyException.java:61)
at
com.fasterxml.jackson.databind.DeserializationContext.handleUnknownProperty(DeserializationContext.java:823)
at
com.fasterxml.jackson.databind.deser.std.StdDeserializer.handleUnknownProperty(StdDeserializer.java:1153)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownProperty(BeanDeserializerBase.java:1589)
at
com.fasterxml.jackson.databind.deser.BeanDeserializerBase.handleUnknownVanilla(BeanDeserializerBase.java:1567)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:294)
at
com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
at
com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4013)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3004)
at com.kfz24.mockingservice.service.impl.MockingProductServiceImpl.getProducts(MockingProductServiceImpl.java:49)
at com.kfz24.mockingservice.controller.MockingProductController.getProducts(MockingProductController.java:29)
Because #SerializedName is from Gson but not Jackson .The equivalent in Jackson is #JsonProperty.
So change all #SerializedName to #JsonProperty. Also , Jackson by default only deserialize the public fields if it does not marked with #JsonProperty.
So for you , I suggest that the simplest way is to make sure all fields are marked with #JsonProperty (e.g. name, brand etc.)
Using jsonschema2pojo you can generate POJO class with Jackson annotations. Choose Jackson 2.x to generate model with Jackson annotations. Source type should be JSON. Below you can see Product class I have generated using this tool. all properties are public, so change them to private and generate getters and setters.
class Product {
#JsonProperty("product_id")
public String productId;
#JsonProperty("sku")
public String sku;
#JsonProperty("display_sku")
public String displaySku;
#JsonProperty("genart_number")
public Integer genartNumber;
#JsonProperty("name")
public String name;
#JsonProperty("description")
public Object description;
#JsonProperty("additional_info_text")
public Object additionalInfoText;
#JsonProperty("product_url")
public String productUrl;
#JsonProperty("image_url")
public String imageUrl;
#JsonProperty("brand")
public String brand;
#JsonProperty("eans")
public List<String> eans = null;
#JsonProperty("manufacturer_product_number")
public String manufacturerProductNumber;
#JsonProperty("data_supplier_number")
public String dataSupplierNumber;
#JsonProperty("pricehammer")
public Boolean pricehammer;
#JsonProperty("buyable")
public Boolean buyable;
#JsonProperty("bulky_good")
public Boolean bulkyGood;
#JsonProperty("risky_good")
public Boolean riskyGood;
#JsonProperty("hazardous_good")
public Boolean hazardousGood;
#JsonProperty("car_specific")
public Boolean carSpecific;
#JsonProperty("has_deposit")
public Boolean hasDeposit;
#JsonProperty("is_exchange_part")
public Boolean isExchangePart;
#JsonProperty("visibility_status")
public String visibilityStatus;
#JsonProperty("deleted")
public Boolean deleted;
#Override
public String toString() {
return "Product{" +
"productId='" + productId + '\'' +
", sku='" + sku + '\'' +
", displaySku='" + displaySku + '\'' +
", genartNumber=" + genartNumber +
", name='" + name + '\'' +
", description=" + description +
", additionalInfoText=" + additionalInfoText +
", productUrl='" + productUrl + '\'' +
", imageUrl='" + imageUrl + '\'' +
", brand='" + brand + '\'' +
", eans=" + eans +
", manufacturerProductNumber='" + manufacturerProductNumber + '\'' +
", dataSupplierNumber='" + dataSupplierNumber + '\'' +
", pricehammer=" + pricehammer +
", buyable=" + buyable +
", bulkyGood=" + bulkyGood +
", riskyGood=" + riskyGood +
", hazardousGood=" + hazardousGood +
", carSpecific=" + carSpecific +
", hasDeposit=" + hasDeposit +
", isExchangePart=" + isExchangePart +
", visibilityStatus='" + visibilityStatus + '\'' +
", deleted=" + deleted +
'}';
}
}
I noticed you wanted to skip result(Object) -> result(Array) levels. See how to do that with Jackson:
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.type.CollectionType;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
public class JsonApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
JsonNode jsonNode = mapper
.reader()
.at("/result/result")
.readTree(new FileInputStream(jsonFile));
CollectionType productsType = mapper.getTypeFactory().constructCollectionType(List.class, Product.class);
List<Product> products = mapper.convertValue(jsonNode, productsType);
System.out.println(products);
}
}
Above code prints:
[Product{productId='1420-131617-82', sku='1420-131617', displaySku='8DD 355 100-411', genartNumber=82, name='Bremsscheibe', description=null, additionalInfoText=null, productUrl='https://www.autoteile5000.de/product/1420-131617-82', imageUrl='https://static.autoteile5000.de/product-images/HLP/4625-462502682-3-255-1548045462267.jpg', brand='HELLA PAGID', eans=[4082300365078], manufacturerProductNumber='8DD 355 100-411', dataSupplierNumber='4625', pricehammer=false, buyable=true, bulkyGood=false, riskyGood=false, hazardousGood=false, carSpecific=true, hasDeposit=false, isExchangePart=false, visibilityStatus='active', deleted=false}]

Deserializing unwrapped/flattened Json in Java

I am building out a client library for a somewhat complex API. However, there is significant commonality between different response objects, which look something like this:
{
"response": "Success",
"delay": 0.241,
"time": 125425234,
"message": null,
"a": "Payloads"
}
{
"response": "AuthFailure",
"delay": 0.112,
"time": 1324515123,
"message": "Wrong password",
"b": 1234
}
{
"response": "Success",
"delay": 0.294,
"time": 12461246123,
"message": null,
"c": True
"d": 245.1
}
I want to factor out the common parts, and hopefully deserialize them into a composition of objects:
Response<AData>
Response<BData>
Response<CDData>
(The class definitions would look something like):
class Response<T> {
final Response response;
final Double delay;
final Timestamp time;
final String message;
final T inner;
...
}
class AData {
final String a;
...
}
class BData {
final int b;
...
}
class BData {
final bool c;
final double d;
...
}
This is a lot like the inverse of the "JsonUnwrapped" Jackson annotation. Inheritence would work too.
Unfortunately, I cannot find a way to do this sensibly in Jackson in a way that would compose with the rest of the ObjectMapper system without writing a significant additional module. Am I missing something? Is there a better way to do this sort of thing?
The problem here is that you (or jackson) need to know what object need to be used to convert request. There is two ways of doing it:
1) use inheritance. This approach is more robust due to Jackson will handle everything for you but this approach requires to add a marker, that will be used by jackson to choose which object type should be used for conversion. Not sure that you can add these markers but the code below should give you an idea of how it can be done.
Its pretty simple -- you just need to add #JsonTypeInfo to configure which field will be used as a marker and #JsonSubTypes to define all the classes that can be used to convert response.
class ResponseA extends BaseResponse {
private String a;
}
class ResponseB extends BaseResponse {
private String b;
}
class ResponseCD extends BaseResponse {
private boolean c;
private double d;
}
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = ResponseA.class, name = "a"),
#JsonSubTypes.Type(value = ResponseB.class, name = "b"),
#JsonSubTypes.Type(value = ResponseCD.class, name = "cd")
})
class BaseResponse {
private String response;
private double delay;
private long time;
private String message;
}
public class DynamicResponseInheritance {
private static final String RESPONSE_A = "{\n" +
" \"type\": \"a\",\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.241,\n" +
" \"time\": 125425234,\n" +
" \"message\": null,\n" +
" \"a\": \"Payloads\"\n" +
"}";
private static final String RESPONSE_B = "{\n" +
" \"type\": \"b\",\n" +
" \"response\": \"AuthFailure\",\n" +
" \"delay\": 0.112,\n" +
" \"time\": 1324515123,\n" +
" \"message\": \"Wrong password\",\n" +
" \"b\": 1234\n" +
"}";
private static final String RESPONSE_CD = "{\n" +
" \"type\": \"cd\",\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.294,\n" +
" \"time\": 12461246123,\n" +
" \"message\": null,\n" +
" \"c\": true,\n" +
" \"d\": 245.1\n" +
"}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);
System.out.println(responseA);
System.out.println(responseB);
System.out.println(responseCD);
}
}
2) implement custom deserializer. Its also pretty simple but in that case you will need to update deserializer if you need to add new class. The benefit of this approach is that you don't need to modify the response.
class ResponseDeserializer extends JsonDeserializer<BaseResponse> {
#Override
public BaseResponse deserialize(JsonParser parser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode root = parser.getCodec().readTree(parser);
JsonNode a = root.get("a");
if (a != null) {
String content = root.toString();
return mapper.readValue(content, ResponseA.class);
}
JsonNode b = root.get("b");
if (b != null) {
String content = root.toString();
return mapper.readValue(content, ResponseB.class);
}
JsonNode c = root.get("c");
if (c != null) {
String content = root.toString();
return mapper.readValue(content, ResponseCD.class);
}
return null;
}
}
class ResponseA extends BaseResponse {
private String a;
}
class ResponseB extends BaseResponse {
private String b;
}
class ResponseCD extends BaseResponse {
private boolean c;
private double d;
}
class BaseResponse {
private String response;
private double delay;
private long time;
private String message;
}
public class DynamicResponseCustomDeserializer {
private static final String RESPONSE_A = "{\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.241,\n" +
" \"time\": 125425234,\n" +
" \"message\": null,\n" +
" \"a\": \"Payloads\"\n" +
"}";
private static final String RESPONSE_B = "{\n" +
" \"response\": \"AuthFailure\",\n" +
" \"delay\": 0.112,\n" +
" \"time\": 1324515123,\n" +
" \"message\": \"Wrong password\",\n" +
" \"b\": 1234\n" +
"}";
private static final String RESPONSE_CD = "{\n" +
" \"response\": \"Success\",\n" +
" \"delay\": 0.294,\n" +
" \"time\": 12461246123,\n" +
" \"message\": null,\n" +
" \"c\": true,\n" +
" \"d\": 245.1\n" +
"}";
public static void main(String []args) throws IOException {
ObjectMapper objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(BaseResponse.class, new ResponseDeserializer());
objectMapper.registerModule(module);
BaseResponse responseA = objectMapper.readValue(RESPONSE_A, BaseResponse.class);
BaseResponse responseB = objectMapper.readValue(RESPONSE_B, BaseResponse.class);
BaseResponse responseCD = objectMapper.readValue(RESPONSE_CD, BaseResponse.class);
System.out.println(responseA);
System.out.println(responseB);
System.out.println(responseCD);
}
}
This is not the inverse of #JsonUnwrapped. this IS #JsonUnwrapped, since the json specifies the variable inside the inner field of Response.
What you want is to tell Jackson what is the Generic construction of your data and you can achieve this with Jackson's internal type system:
This is the class definitions I used:
public class Response<T> {
public String response;
public Double delay;
public Timestamp time;
public String message;
#JsonUnwrapped
public T inner;
}
public class AData {
public String a;
}
public class BData {
public int b;
}
public class CData {
public boolean c;
public double d;
}
This is how you tell Jackson of generic construct:
ObjectMapper mapper = new ObjectMapper();
TypeFactory f = mapper.getTypeFactory();
JavaType responseAData = f.constructParametrizedType(Response.class, Response.class, AData.class);
Then deserialization can take place in the usual manner:
try (InputStream is = new FileInputStream("C://Temp/xx.json")) {
Response<AData> r = (Response<AData>) mapper.readValue(is, responseAData);
System.out.println(r.inner.a );
} catch (Exception e) {
e.printStackTrace();
}
Given input as specified in question, I got output as Payloads
The conly caveat here is that your client needs to know in advance the type of response

adding validaitons while json is getting deserialized jackson

I have a json structure as shown below:
{
"clientId": 111,
"clientName": "abc",
"holder": [
{
"clientKey": "abc1",
"clientValue": {"hello" : "world"}
},
{
"clientKey": "abc2",
"recordValue": {}
}
]
}
I am deserializing my above JSON to my POJO using Jackson. Below is my POJO class where everything will get serialized.
import org.codehaus.jackson.annotate.JsonProperty;
public class DataRequest {
#JsonProperty("clientId")
private int clientId;
#JsonProperty("clientName")
private String clientName;
#JsonProperty("holder")
private List<ClientHolder> holder;
// getters and setters
public static class ClientHolder {
#JsonProperty("clientKey")
private String clientKey;
#JsonProperty("clientValue")
private Map<String, Object> clientValue;
// getters and setters
}
}
Is there any way I can have some sort of annotations in jackson that can do the validation while it is getting deserialized instead of doing the validations check after everything is deserialized? I want to validate below things:
clientId should be greater than zero.
clientName should never be null or empty string.
holder List should never be empty.
clientKey should never be null or empty string.
clientValue should never be null or empty as well.
Right now I am validating here:
private void validate(DataRequest request) {
if (request.getSchemaId() <= 0) {
// throw some exception
}
if (request.getClientName() == null || request.getClientName().isEmpty()) {
// throw some exception
}
// now I am not sure how should I do the validation for each
// clientKey and clientValue here efficiently
// if this is the only way we can do validations
}
You can consider using the Jackson Builder pattern support to build your model. Then you can put your validation code in the builder's build method. The build method will be called as a part of deserialisation. Here is a complete example with Jackson 2.X.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class JacksonBuilderValidation {
final static String JSON = "{\n"
+ " \"clientId\": 111,\n"
+ " \"clientName\": \"abc\",\n"
+ " \"recordValue\": [\n"
+ " {\n"
+ " \"clientKey\": \"abc1\",\n"
+ " \"clientValue\": {\"hello\" : \"world\"}\n"
+ " },\n"
+ " {\n"
+ " \"clientKey\": \"abc2\",\n"
+ " \"clientValue\": {}\n"
+ " }\n"
+ " ]}";
#JsonDeserialize(builder = JacksonBuilderValidation.DataRequest.Builder.class)
static class DataRequest {
private int clientId;
private String clientName;
private List<ClientHolder> recordValue;
private DataRequest(Builder builder) {
this.clientId = builder.clientId;
this.clientName = builder.clientName;
this.recordValue = builder.recordValue;
}
#Override
public String toString() {
return "DataRequest{" +
"clientId=" + clientId +
", clientName='" + clientName + '\'' +
", recordValue=" + recordValue +
'}';
}
static class ClientHolder {
public String clientKey;
public Map<String, Object> clientValue;
#Override
public String toString() {
return "ClientHolder{" +
"clientKey='" + clientKey + '\'' +
", clientValue=" + clientValue +
'}';
}
}
#JsonPOJOBuilder(withPrefix = "")
static class Builder {
private int clientId;
private String clientName;
private List<ClientHolder> recordValue;
Builder clientId(int clientId) {
this.clientId = clientId;
return this;
}
Builder clientName(String clientName) {
this.clientName = clientName;
return this;
}
Builder recordValue(List<ClientHolder> recordValue) {
this.recordValue = recordValue;
return this;
}
DataRequest build() {
final DataRequest dataRequest = new DataRequest(this);
// write validation code here
System.out.println("Is record value empty? "
+ dataRequest.recordValue.isEmpty());
return dataRequest;
}
}
}
public static void main(String[] args) throws IOException {
final ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.readValue(JSON, DataRequest.class));
}
}
Output:
Is record value empty? false
DataRequest{clientId=111, clientName='abc', recordValue=[ClientHolder{clientKey='abc1', clientValue={hello=world}}, ClientHolder{clientKey='abc2', clientValue={}}]}

How to ignore a specific field while parsing a JSON into map

I want to parse the below JSON into POJO. I am using jackson to parse the json.
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
In the above JSON, the fields "totalSize", "done", "records" and "attributes" are known fields. Whereas, "AccountId", "Id" and "ProposalID" are unknown fields. And in the above JSON, I don't need "attributes" to be part of my bean object.
And here is equivalent bean class for my JSON
public class Result {
private int totalSize;
private boolean done;
private List<Map<String, String>> records;
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
public List<Map<String,String>> getRecords() {
return records;
}
public void setRecords(List<Map<String, String>> records) {
this.records = records;
}
}
Hence there are unknown fields in the records element I just used List to get the results element in bean. Here in this Map, I don't want the field "attributes". How can I ignore this while parsing?
And below is the exception that I am getting as attributes is not a string element.
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: [B#66fdec9; line: 1, column: 40] (through reference chain: com.sample.json.Result["records"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:164)
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:691)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:46)
at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:11)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:430)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:312)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:26)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:227)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:204)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:23)
UPDATE 2015/08/29:
As you have commented that
I achieved dynamic field support by parsing the JSON into map. Ignoring bad JSON element is what pending
I suggest that you should process original JSONObject to remove the "attributes" element from it.
Original JSONObject, for example:
{
"totalSize": 4,
"done": true,
"records": [
{
"attributes": {
"type": "oppor",
"url": "/service/oppor/456"
},
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
}
]
}
After process, new JSONObject will be like the following:
{
"records": {
"AccountId": "123",
"Id": "456",
"ProposalID": "103"
},
"totalSize": 4,
"done": true
}
Use the code as the following:
JSONObject jsonObject;
try {
jsonObject = new JSONObject(jsonString1);
JSONArray jsonArray = new JSONArray(jsonObject.get("records").toString());
JSONObject jsonObject1 = jsonArray.getJSONObject(0);
jsonObject1.remove("attributes");
jsonObject.put("records", jsonObject1);
} catch (JSONException e) {
e.printStackTrace();
}
Then, use your own code that achieved dynamic field support by parsing the JSON into map.
END OF UPDATE 2015/08/29
I suggest that you use Gson and transient in this case
Like this
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Result result1 = gson.fromJson(jsonString1, Result.class);
Your classes, pay attention to transient:
public class Result {
private int totalSize;
private boolean done;
private List<Record> records;
}
public class Record {
private transient Map<String, String> attributes;
private int AccountId;
private int Id;
private int ProposalID;
}
You will get the result:
P/S: I tested in Android Studio :)
UPDATE:
String jsonString1 = "{\n" +
" \"totalSize\": 4,\n" +
" \"done\": true,\n" +
" \"records\": [\n" +
" {\n" +
" \"attributes\": {\n" +
" \"type\": \"oppor\",\n" +
" \"url\": \"/service/oppor/456\"\n" +
" },\n" +
" \"AccountId\": \"123\",\n" +
" \"Id\": \"456\",\n" +
" \"ProposalID\": \"103\"\n" +
" }\n" +
" ]\n" +
"}";
Gson gson = new Gson();
Object object = gson.fromJson(jsonString1, Object.class);
Map<String, String> stringMap = (Map<String, String>) object;
Result myResult = new Result();
Iterator entries = stringMap.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
String key = entry.getKey().toString();
String value = entry.getValue().toString();
switch (key) {
case "totalSize":
myResult.totalSize = (int) Double.parseDouble(entry.getValue().toString());
break;
case "done":
myResult.done = Boolean.valueOf(entry.getValue().toString());
break;
case "records":
try{
Object object1 = entry.getValue();
List<Object> objectList = (List<Object>) object1;
Map<String, Object> stringMap2 = (Map<String, Object>) objectList.get(0);
Map<String, String> recordMap = new HashMap<>();
Iterator entries2 = stringMap2.entrySet().iterator();
while (entries2.hasNext()) {
Map.Entry entry2 = (Map.Entry) entries2.next();
String key2 = entry2.getKey().toString();
String value2 = entry2.getValue().toString();
if (!"attributes".equals(key2)) {
recordMap.put(key2, value2);
}
entries2.remove();
}
myResult.records = recordMap;
} catch (Exception e) {
e.printStackTrace();
}
break;
}
entries.remove();
}
Classes:
public class Result {
private int totalSize;
private boolean done;
private Map<String, String> records;
}
Debug result:
1) Create a Record class object
2) Add #JsonIgnore Annotation on fields you won't
public class Result {
private int totalSize;
private boolean done;
private Record records;
[..]
}
public class Record {
#JsonIgnore
private Map<String, String> attributes;
private int accountID;
private int id;
private int approvalID;
[..]
}
Create a new POJO class for attributes,
public class Result {
private int totalSize;
private boolean done;
private List<Attributes> records;
// Your Getters & Setters
}
public class Attributes{
List<Map<String,String>> attributes;
// Add other variables if necessary like AccountId, etc.,
// Your Getters & Setters
}
I would suggest to use [Google gson API][1]'s #Expose annotation. (if that is allowed in your environment).
You can simply annotate the fields(with #Expose) which are required in your generated json file, and leave it other fields. And during generating json, use API method, excludeFieldsWithoutExposeAnnotation.
Sample example can be seen here.
Note : In your example, treat your Result as Main POJO, and records is another POJO which has attributes,accountId etc fields.
Then there is has-a relationship (Java composition) between them.
And after that, you can invoke Json to pojo conversion like below--
com.google.gson.Gson gson = new com.google.gson.GsonBuilder()
.excludeFieldsWithoutExposeAnnotation().create();
Result result= gson.fromJson(yourjsonString, Result.class);
If you have specific fields you don't want to map, u can use #JsonIgnore annotation over the field name
public class MyJsonObj{
#JsonProperty("name")
String fieldName
#JsonIgnore
String fieldNameIgnored;
}
If you want to ignore all the fields not mentioned in your POJO, you can use
#JsonIgnoreProperties annotation over class
#JsonIgnoreProperties(ignoreUnknown = true)
public class MyJsonObj{
strong text
}

Categories