The diets object in your json is not a List. Its a List of key-value pair with key "diet" and value a diet object. So you have three options here.
One is to create a wrapper object say DietWrapper and use List of diet wrapper in User like
@JsonRootName(value = "user")
class User {
@JsonProperty(value = "id")
private long id;
@JsonProperty(value = "diets")
private List<DietWrapper> diets;
//Getter & Setters
}
class DietWrapper {
@JsonProperty(value = "diet")
Diet diet;
}
Second option is to keep diest as simple list of maps like List>
@JsonRootName(value = "user")
class User {
@JsonProperty(value = "id")
private long id;
@JsonProperty(value = "diets")
private List<Map<String, Diet>> diets;
//Getter & Setters
}
Third option is to use a custom deserializer which would ignore your diet class.
@JsonRootName(value = "user")
class User {
@JsonProperty(value = "id")
private long id;
@JsonProperty(value = "diets")
@JsonDeserialize(using = DietDeserializer.class)
private List<Diet> diets;
//Getter & Setters
}
class DietDeserializer extends JsonDeserializer<List<Diet>> {
@Override
public List<Diet> deserialize(JsonParser jsonParser,
DeserializationContext deserializationContext) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(jsonParser);
List<Diet> diets = mapper.convertValue(node.findValues("diet"), new TypeReference<List<Diet>>() {});
return diets;
}
}
Answer from Syam S on Stack OverflowYou can deserialize directly to a list by using the TypeReference wrapper. An example method:
public static <T> T fromJSON(final TypeReference<T> type,
final String jsonPacket) {
T data = null;
try {
data = new ObjectMapper().readValue(jsonPacket, type);
} catch (Exception e) {
// Handle the problem
}
return data;
}
And is used thus:
final String json = "";
Set<POJO> properties = fromJSON(new TypeReference<Set<POJO>>() {}, json);
TypeReference Javadoc
Another way is to use an array as a type, e.g.:
ObjectMapper objectMapper = new ObjectMapper();
MyPojo[] pojos = objectMapper.readValue(json, MyPojo[].class);
This way you avoid all the hassle with the Type object, and if you really need a list you can always convert the array to a list by:
List<MyPojo> pojoList = Arrays.asList(pojos);
IMHO this is much more readable.
And to make it be an actual list (that can be modified, see limitations of Arrays.asList()) then just do the following:
List<MyPojo> mcList = new ArrayList<>(Arrays.asList(pojos));
@JsonDeserialize(contentAs=Bar.class)
List<IBar> bars;
Why don't you just use a TypeReference ?
For instance...
Json file test.json in /your/path/:
[{"s":"blah"},{"s":"baz"}]
Main class in package test:
public class Main {
public static void main(String[] args) {
ObjectMapper mapper = new ObjectMapper();
try {
List<IBar> actuallyFoos = mapper.readValue(
new File("/your/path/test.json"), new TypeReference<List<Foo>>() {
});
for (IBar ibar : actuallyFoos) {
System.out.println(ibar.getClass());
}
}
catch (Throwable t) {
t.printStackTrace();
}
}
static interface IBar {
public String getS();
public void setS(String s);
}
static class Foo implements IBar {
protected String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
static class Bar implements IBar {
protected String s;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
}
}
Output of the main method:
class test.Main$Foo
class test.Main$Foo
First of all, If you registered your custom deserializer using annotation on the bean CustomClass then the deserializer should handle one instance of CustomClass and not a collection and thus should be defined:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
@Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException
{
...
}
}
and now you can use Jackson's type factory to pass the mapper the required type information
JavaType customClassCollection = objectMapper.getTypeFactory().constructCollectionType(List.class, CustomClass.class);
List<CustomClass> beanList = (List<CustomClass>)objectMapper.readValue(stringBean, customClassCollection);
I worked it out by adding a custom deserializer to an attribute in my model class and using JsonDeserialize annotation's contentUsing() method, like so:
@JsonDeserialize(contentUsing = MyCustomDeserializer.class)
private List<CustomClass> customClassObjList;
where MyCustomDeserializer class is a custom Jackson JSON deserializer defined as:
public class MyCustomDeserializer extends JsonDeserializer<CustomClass> {
@Override
public CustomClass deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
...
}
}
Since you are using Jackson I think what you need is JsonDeserializer class (javadoc).
You can implement it like this:
public class ListOrObjectGenericJsonDeserializer<T> extends JsonDeserializer<List<T>> {
private final Class<T> cls;
public ListOrObjectGenericJsonDeserializer() {
final ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
this.cls = (Class<T>) type.getActualTypeArguments()[0];
}
@Override
public List<T> deserialize(final JsonParser p, final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final ObjectCodec objectCodec = p.getCodec();
final JsonNode listOrObjectNode = objectCodec.readTree(p);
final List<T> result = new ArrayList<T>();
if (listOrObjectNode.isArray()) {
for (JsonNode node : listOrObjectNode) {
result.add(objectCodec.treeToValue(node, cls));
}
} else {
result.add(objectCodec.treeToValue(listOrObjectNode, cls));
}
return result;
}
}
...
public class ListOrObjectResultItemJsonDeserializer extends ListOrObjectGenericJsonDeserializer<ResultItem> {}
Next you need to annotate your POJO field. Let's say you have classes like Result and ResultItem:
public class Result {
// here you add your custom deserializer so jackson will be able to use it
@JsonDeserialize(using = ListOrObjectResultItemJsonDeserializer.class)
private List<ResultItem> result;
public void setResult(final List<ResultItem> result) {
this.result = result;
}
public List<ResultItem> getResult() {
return result;
}
}
...
public class ResultItem {
private String value;
public String getValue() {
return value;
}
public void setValue(final String value) {
this.value = value;
}
}
Now you can check your deserializer:
// list of values
final String json1 = "{\"result\": [{\"value\": \"test\"}]}";
final Result result1 = new ObjectMapper().readValue(json1, Result.class);
// one value
final String json2 = "{\"result\": {\"value\": \"test\"}}";
final Result result2 = new ObjectMapper().readValue(json2, Result.class);
result1 and result2 contain the same value.
You can achieve what you want with a configuration flag in Jackson's ObjectMapper:
ObjectMapper mapper = Jackson2ObjectMapperBuilder.json()
.featuresToEnable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
.build();
Just set this ObjectMapper instance to your RestTemplate as explained in this answer, and in the class you are deserializing to, always use a collection, i.e. a List:
public class Response {
private List<Result> result;
// getter and setter
}
Looks like you are looking for the ACCEPT_SINGLE_VALUE_AS_ARRAY deserialization feature.
Feature that determines whether it is acceptable to coerce non-array (in JSON) values to work with Java collection (arrays,
java.util.Collection) types. If enabled, collection deserializers will try to handle non-array values as if they had "implicit" surrounding JSON array. This feature is meant to be used for compatibility/interoperability reasons, to work with packages (such as XML-to-JSON converters) that leave out JSON array in cases where there is just a single element in array.Feature is disabled by default.
It could be enabled either in ObjectMapper:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY);
Or via the @JsonFormat annotation:
@JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
private List<Foo> oneOrMany;
For illustration purposes, consider the following JSON documents:
{
"oneOrMany": [
{
"value": "one"
},
{
"value": "two"
}
]
}
{
"oneOrMany": {
"value": "one"
}
}
It could be the deserialized to the following classes:
@Data
public class Foo {
private List<Bar> oneOrMany;
}
@Data
public class Bar {
private String value;
}
Just ensure the feature is enabled in your ObjectMapper or your field is annotated with @JsonFormat(with = Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).
And in case you are looking for the equivalent feature for serialization, refer to WRITE_SINGLE_ELEM_ARRAYS_UNWRAPPED.
I would recommend using Object as your data type for the property which is dynamic. So Here is my sample.
import java.util.Arrays;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class MainObject {
private Object key_is_same;
public Object getKey_is_same() {
return key_is_same;
}
public void setKey_is_same(Object key) {
this.key_is_same = key;
}
public static class KeyObject {
private String inner;
public String getInner() {
return inner;
}
public void setInner(String inner) {
this.inner = inner;
}
}
public static void main(String...s) throws JsonProcessingException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
public static void main(String...s) throws IOException {
MainObject main = new MainObject();
KeyObject k = new KeyObject();
k.setInner("val1");
main.setKey_is_same(k);
ObjectMapper om = new ObjectMapper();
System.out.println(om.writeValueAsString(main));
main.setKey_is_same(Arrays.asList(k, k));
System.out.println(om.writeValueAsString(main));
// Deserialize
MainObject mainWithObject = om.readValue("{\"key_is_same\":{\"inner\":\"val1\"}}", MainObject.class);
MainObject mainWithList = om.readValue("{\"key_is_same\":[{\"inner\":\"val1\"},{\"inner\":\"val1\"}]}", MainObject.class);
if(mainWithList.getKey_is_same() instanceof java.util.List) {
((java.util.List) mainWithList.getKey_is_same()).forEach(System.out::println);
}
}
}
}
Output
{"key_is_same":{"inner":"val1"}}
{"key_is_same":[{"inner":"val1"},{"inner":"val1"}]}
We need to implement custom deserialiser. Because we want to skip one inner field our implementation should:
{- skip start object"any_field_name"- skip any field name. We assume that we have only one inner field.[{}, ..., {}]- use default deserialiser forList.}- skip end object
Using above concept implementation should be easy:
public class InnerListDeserializer extends JsonDeserializer<List> implements ContextualDeserializer {
private final JavaType propertyType;
public InnerListDeserializer() {
this(null);
}
public InnerListDeserializer(JavaType propertyType) {
this.propertyType = propertyType;
}
@Override
public List deserialize(JsonParser p, DeserializationContext context) throws IOException {
p.nextToken(); // SKIP START_OBJECT
p.nextToken(); // SKIP any FIELD_NAME
List list = context.readValue(p, propertyType);
p.nextToken(); // SKIP END_OBJECT
return list;
}
@Override
public JsonDeserializer<?> createContextual(DeserializationContext context, BeanProperty property) {
return new InnerListDeserializer(property.getType());
}
}
Let's assume we have JSON payload like this:
{
"transaction": {
"items": {
"item": [
{
"itemNumber": "193487654",
"itemDescription": "Widget",
"itemPrice": "599.00",
"itemQuantity": "1",
"itemBrandName": "ACME",
"itemCategory": "Electronics",
"itemTax": "12.95"
},
{
"itemNumber": "193487654",
"itemDescription": "Widget",
"itemPrice": "599.00",
"itemQuantity": "1",
"itemBrandName": "ACME",
"itemCategory": "Electronics",
"itemTax": "12.95"
}
]
},
"name": "Pickle Rick"
}
}
Above JSON we can map to below POJO classes:
@JsonRootName("transaction")
public class Transaction {
private String name;
private List<Item> items;
@JsonDeserialize(using = InnerListDeserializer.class)
public List<Item> getItems() {
return items;
}
// getters, setters, toString
}
public class Item {
private String itemNumber;
// getters, setters, toString
}
To show it works for many different models let's introduce one more JSON payload:
{
"product": {
"products": {
"innerArray": [
{
"id": "1234"
}
]
}
}
}
and two more POJO classes:
@JsonRootName("product")
class Product {
private List<ProductItem> products;
@JsonDeserialize(using = InnerListDeserializer.class)
public List<ProductItem> getProducts() {
return products;
}
// getters, setters, toString
}
class ProductItem {
private String id;
// getters, setters, toString
}
Now we can test our solution:
import com.fasterxml.jackson.annotation.JsonRootName;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class JSoupTest {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
mapper.enable(DeserializationFeature.UNWRAP_ROOT_VALUE);
mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
File jsonFile = new File("Path to 1-st JSON").getAbsoluteFile();
File jsonFile1 = new File("Path to 2-nd JSON").getAbsoluteFile();
System.out.println(mapper.readValue(jsonFile, Transaction.class));
System.out.println(mapper.readValue(jsonFile1, Product.class));
}
}
Above example prints:
Transaction{items=[Item{itemNumber=193487654}, Item{itemNumber=193487654}], name='Pickle Rick'}
Product{products=[ProductItem{id='1234'}]}
For more info read:
- Custom Jackson Deserializer Getting Access to Current Field Class
- Getting Started with Custom Deserialization in Jackson
- Jackson Exceptions – Problems and Solutions
- Jackson UNWRAP_ROOT_VALUE
- Configuring ObjectMapper in Spring
It seems that @JsonUnwrapped is what I need.
https://www.baeldung.com/jackson-annotations
@JsonUnwrapped defines values that should be unwrapped/flattened when serialized/deserialized.
Let's see exactly how that works; we'll use the annotation to unwrap the property name:
public class UnwrappedUser { public int id; @JsonUnwrapped public Name name; public static class Name { public String firstName; public String lastName; } }Let's now serialize an instance of this class:
@Test public void whenSerializingUsingJsonUnwrapped_thenCorrect() throws JsonProcessingException, ParseException { UnwrappedUser.Name name = new UnwrappedUser.Name("John", "Doe"); UnwrappedUser user = new UnwrappedUser(1, name); String result = new ObjectMapper().writeValueAsString(user); assertThat(result, containsString("John")); assertThat(result, not(containsString("name"))); }Here's how the output looks like – the fields of the static nested class unwrapped along with the other field:
{ "id":1, "firstName":"John", "lastName":"Doe" }
So, it should be something like:
public class TrasactionDTO {
private List<Item> items;
...
}
public static class Item {
@JsonUnwrapped
private InnerItem innerItem;
...
}
public static class InnerItem {
private String itemNumber;
...
}
I use jackson 1.9.12 and there are no problems serialize and deserialize HashMap.
Attachments:
import java.util.Map;
public class Attachments
{
//@JsonDeserialize(as=HashMap.class) // use this if you want a HashMap
public Map<String, AttachmentFile> attachments;
public Attachments() {
}
public Attachments(
final Map<String, AttachmentFile> attachments
) {
this.attachments = attachments;
}
}
AttachmentFile:
public class AttachmentFile
{
public String content;
public String name;
public String type;
public AttachmentFile() {
}
public AttachmentFile(
final String content,
final String name,
final String type
) {
this.content = content;
this.name = name;
this.type = type;
}
}
Test:
import java.util.HashMap;
import java.util.Map;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.SerializationConfig;
import org.junit.Assert;
import org.junit.Test;
public class AttachmentsTest
{
@Test
public void test()
{
try {
final Map<String, AttachmentFile> attachments = new HashMap<String, AttachmentFile>();
attachments.put(
"file1",
new AttachmentFile(
"",
"sample.json",
"application/json"
)
);
attachments.put(
"file2",
new AttachmentFile(
"",
"myspreadsheet.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
);
final Attachments inputData = new Attachments();
inputData.attachments = attachments;
final ObjectMapper jsonMapper = new ObjectMapper();
jsonMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
final String jsonString = jsonMapper.writeValueAsString(inputData);
//System.out.println(jsonString);
final Attachments outputData = jsonMapper.readValue(jsonString, inputData.getClass());
Assert.assertNotNull(outputData);
Assert.assertEquals(inputData.attachments.size(), outputData.attachments.size());
Assert.assertEquals(inputData.attachments.get("file1").name, outputData.attachments.get("file1").name);
Assert.assertEquals(inputData.attachments.get("file2").name, outputData.attachments.get("file2").name);
} catch (final Exception e) {
Assert.fail(e.getMessage());
}
}
}
You do not need a custom deserializer.
Using jacksons @JsonAnySetter annotation, you can write a method in your attachment class that looks like this
class Attachment
{
ArrayList files = new ArrayList();
@JsonAnySetter
public void setFile(String name, Object value)
{
files.add(value);
}
}
You may have to tweak that code (using more annotations), to make sure that value is deserialized as AttachmentFile. But I think you get the basic idea.