I get [null, null] as output when I run the code. Any thoughts ?
Here is my deserialized class :
package TestPackage;
import org.codehaus.jackson.annotate.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class DeSerializeJSON {
private String url;
private String ip;
public DeSerializeJSON(){}
//I have deserialized the rest.
#Override
public String toString(){
return url + "," + ip;
}
}
My test class is as below :
ObjectMapper mapper = new ObjectMapper();
try{
List<DeSerializeJSON> urls = Arrays.asList(mapper.readValue(new File("C:\\Test\\Output.txt").getAbsoluteFile(), DeSerializeJSON[].class));
PrintStream out = new PrintStream(new FileOutputStream("C:\\Test\\ipUrlOutput.txt"));
System.setOut(out);
System.out.println(urls);
What's wrong
Your Java class does not match your JSON, that's why you have null values and you don't get any mapping exceptions because your class is annotated with #JsonIgnoreProperties(ignoreUnknown = true).
How to fix it
Consider the JSON document you posted on your comment:
[
{
"items": [
{
"Inputs": {
"url": "some.com"
},
"Outputs": {
"ip": "51.70.182.125",
"redirectipaddress": "16.150.210.199",
"httpstatuscode": "200",
"processes": {},
"Steps": [],
"Queues": {},
"Outcomes": {
"language": null,
"type": null
}
}
}
]
}
]
It requires the following classes to be parsed:
public class ItemsWrapper {
#JsonProperty("items")
private List<Item> items;
// Default constructor, getters and setters
}
public class Item {
#JsonProperty("Inputs")
private Input input;
#JsonProperty("Outputs")
private Output output;
// Default constructor, getters and setters
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Input {
#JsonProperty("url")
private String url;
// Default constructor, getters and setters
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class Output {
#JsonProperty("ip")
private String ip;
// Default constructor, getters and setters
}
Then you can parse the JSON using ObjectMapper:
String json = "[\n" +
" {\n" +
" \"items\": [\n" +
" {\n" +
" \"Inputs\": {\n" +
" \"url\": \"some.com\"\n" +
" },\n" +
" \"Outputs\": {\n" +
" \"ip\": \"51.70.182.125\",\n" +
" \"redirectipaddress\": \"16.150.210.199\",\n" +
" \"httpstatuscode\": \"200\",\n" +
" \"processes\": {},\n" +
" \"Steps\": [],\n" +
" \"Queues\": {},\n" +
" \"Outcomes\": {\n" +
" \"language\": null,\n" +
" \"type\": null\n" +
" }\n" +
" }\n" +
" }\n" +
" ]\n" +
" }\n" +
"]";
ObjectMapper mapper = new ObjectMapper();
ItemsWrapper[] itemsWrappers = mapper.readValue(json, ItemsWrapper[].class);
Try using this com.fasterxml.jackson.core.type.TypeReference;
List<DeSerializeJSON> urls = mapper.readValue(new File("C:\\Test\\Output.txt").getAbsoluteFile(), new TypeRefference<List<DeSerializeJSON>>(){});
Related
I am working on a JSON Serialisation and i have a scenerio where a field in JSON can be a String/ List of Strings. I am not sure how to serialise such object.
#Data
public class NonNullRatio extends Constraint {
#NonNull private List<String> column;
#NonNull private Assertion assertion;
}
The input JSON Could be -
" \"constraints\": [\n" +
" {\n" +
" \"type\": \"NonNullRatio\",\n" +
" \"column\": [\"transaction_status\", \"user_charge_id\", \"psp_transaction_id\"],\n" +
" \"assertion\": {\n" +
" \"type\": \"LowerLimit\",\n" +
" \"threshold\": 1\n" +
" }\n" +
" },\n" +
OR
" \"constraints\": [\n" +
" {\n" +
" \"type\": \"NonNullRatio\",\n" +
" \"column\": \"ufi\",\n" +
" \"assertion\": {\n" +
" \"type\": \"LowerLimit\",\n" +
" \"threshold\": 1\n" +
" }\n" +
" },\n" +
You need to tell your Jackson to accept String as Array.
You can do this using -
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
Your POJO Should look like -
#Data
public class NonNullRatio extends Constraint {
#JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
#NonNull private List<String> column;
#NonNull private Assertion assertion;
}
You could configure Jackson ObjectMapper using this option to accept single value as array.
new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true)
Example -
class SomeClass{
private List<String> values;
// getter setters
#Override
public String toString() {
return "SomeClass{" +
"values=" + values +
'}';
}
}
public class TestJSON {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
String jsonString = "{\"values\":\"A\"}";
System.out.println(objectMapper.readValue(jsonString, SomeClass.class));
jsonString = "{\"values\":[\"A\",\"B\"]}";
System.out.println(objectMapper.readValue(jsonString, SomeClass.class));
}
}
Outputs
SomeClass{values=[A]}
SomeClass{values=[A, B]}
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.
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}]
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
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'}}}