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 Overflow
🌐
Davismol
davismol.net › 2015 › 03 › 05 › jackson-json-deserialize-a-list-of-objects-of-subclasses-of-an-abstract-class
Jackson JSON: deserialize a list of objects of subclasses of an abstract class | Dede Blog
March 5, 2015 - Override deserialize method and call parser.readValueAsTree. check node and type and then instantiate accordingly. If you are using object mapper you need to add a module and register the module to ObjectMapper ... the sample in the article almost works with lastest jackson. you need to annotate the ctor: @JsonCreator(mode = JsonCreator.Mode.DEFAULT) public MySubItemA(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("itemAProperty1") String p1,@JsonProperty("itemAProperty2") String p2) { super(id, name); this.itemAProperty1 = p1; this.itemAProperty2 = p2; }
🌐
Java Guides
javaguides.net › 2019 › 04 › jackson-list-set-and-map-serialization-and-deseialization-in-java-example.html
Jackson - List, Set and Map Serialization and Deserialization in Java Examples
April 24, 2019 - In our previous article, we have learned how to use the Jackson API for binding JSON data to Plain Old Java Object (POJO) and vice versa. In this article, I show you how to use the Jackson API to serialize and deserialize the java collection objects such as List, Set, and Map.
🌐
Baeldung
baeldung.com › home › json › jackson › jackson – unmarshall to collection/array
Jackson - Unmarshall to Collection/Array | Baeldung
April 26, 2024 - There are two ways to help Jackson understand the right type information – we can either use the TypeReference provided by the library for this very purpose: @Test public void givenJsonArray_whenDeserializingAsListWithTypeReferenceHelp_thenCorrect() throws JsonParseException, JsonMappingException, IOException { ObjectMapper mapper = new ObjectMapper(); List<MyDto> listOfDtos = Lists.newArrayList( new MyDto("a", 1, true), new MyDto("bc", 3, false)); String jsonArray = mapper.writeValueAsString(listOfDtos); List<MyDto> asList = mapper.readValue( jsonArray, new TypeReference<List<MyDto>>() { }); assertThat(asList.get(0), instanceOf(MyDto.class)); }
Top answer
1 of 1
17

You can specify the deserializer class with which to deserialize the elements of the list with the contentUsing attribute of the @JsonDeserializer annotation.

public class Citizen {
    ...
    @JsonDeserializer(contentUsing=MyListDeserializer.class) 
    public void setTickets(List<Tickets> tickets) {
        this.tickets = tickets;
    }
}

Make your deserializer extend JsonDeserializer<BaseClass> and have a attribute in the BaseClass that stores the actual type of the concrete class.

abstract class BaseTickets {
    String ticketType;
    public String getTicketType()
}

public class MyListDeserializer extends JsonDeserializer<BaseTickets> {

    @Override
    public BaseTickets deserialize(JsonParser jsonParser, DeserializationContext arg1) throws IOException, JsonProcessingException {
        ObjectCodec oc = jsonParser.getCodec();
        JsonNode node = oc.readTree(jsonParser);
        Iterator<JsonNode> elements = node.getElements();
        for (; elements.hasNext();) {
            String type = (String) elements.next().get("ticketType");

            if (type.equals()){
               //create concrete type here
            }
        }
     }

Or if you want a single deserializer for all List types with no common base class, then use the using attribute, have MyListDeserializer extend JsonDeserialize<Object>. For determining the type of list element you would have to write a custom serializer that adds the type information to the list which can then be used in the generic deserializer.

public class Citizen {
    ...
    @JsonDeserializer(using=MyListDeserializer.class)
    @JsonSerializer(using=MyListSerializer.class) 
    public void setTickets(List<Tickets> tickets) {
        this.tickets = tickets;
    }
}

public class MyListSerializer extends JsonSerializer<Object> {

    @Override
    public void serialize(Object list, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
        @SuppressWarnings("rawtypes")
        jgen.writeStartObject();
        String type = getListType(list);
        jgen.writeStringField("listType", type);
        jgen.writeObjectField("list", list)
    }
}
Find elsewhere
Top answer
1 of 2
8

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.

2 of 2
6

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
}
🌐
Medium
cowtowncoder.medium.com › jackson-tips-custom-list-serialization-4566f734c58d
Jackson Tips: custom List serialization | by @cowtowncoder | Medium
May 31, 2021 - It is certainly possible to register serializers and deserializers for structured types, too, using Jackson Modules, but you do need to implement Serializers provider if taking that route. But how about using @JsonSerialize annotation...
🌐
Stack Abuse
stackabuse.com › definitive-guide-to-jackson-objectmapper-serialize-and-deserialize-java-objects
Definitive Guide to Jackson ObjectMapper - Serialize and Deserialize Java Objects
October 27, 2023 - We've covered the ObjectMapper ... and deserialization of Java Objects and JSON data. We've first taken a look at how to install Jackson, and then dived into converting JSON to Java Objects - from strings, files, HTTP Responses, InputStreams and byte arrays. Then we explored conversion of JSON to Java lists and maps. We've covered the @JsonProperty and @JsonAlias annotations to "bridge" ...
🌐
CodeGym
codegym.cc › courses › java collections › json serialization frameworks
Course Java Collections - Lecture: JSON serialization frameworks
"But when you deserialize a JSON array, what object should you create: an ArrayList or a LinkedList?" "Right. If a class member is an interface (e.g. public List<Cat> cats), what object should be attributed to it?" "We can add additional annotations to the field or explicitly indicate the target classes during deserialization.
Top answer
1 of 2
5

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.

2 of 2
0

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"}]}
Top answer
1 of 3
6

We need to implement custom deserialiser. Because we want to skip one inner field our implementation should:

  1. { - skip start object
  2. "any_field_name" - skip any field name. We assume that we have only one inner field.
  3. [{}, ..., {}] - use default deserialiser for List.
  4. } - 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:

  1. Custom Jackson Deserializer Getting Access to Current Field Class
  2. Getting Started with Custom Deserialization in Jackson
  3. Jackson Exceptions – Problems and Solutions
  4. Jackson UNWRAP_ROOT_VALUE
  5. Configuring ObjectMapper in Spring
2 of 3
4

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;
    ...
}
🌐
GitHub
gist.github.com › 2285866
Jacskson-xml deserialize List problem · GitHub
Jacskson-xml deserialize List problem. GitHub Gist: instantly share code, notes, and snippets.
🌐
CodeMiner42
blog.codeminer42.com › home › posts › serialization and deserialization in java using jackson
Serialization and Deserialization in Java using Jackson - The Miners
May 29, 2023 - The @JsonProperty annotation is ... and deserialization. By default when we serialize an object, Jackson will map JSON keys matching the name of the object’s attributes. To change this behavior we use @JsonProperty on the fields.
Top answer
1 of 1
1

If you have a field in each object that can be used to recognize the object to build you can use the annotations @JsonTypeInfo:

Annotation used for configuring details of if and how type information is used with JSON serialization and deserialization, to preserve information about actual class of Object instances. This is necessarily for polymorphic types, and may also be needed to link abstract declared types and matching concrete implementation.

and @JsonSubTypes:

Annotation used with JsonTypeInfo to indicate sub types of serializable polymorphic types, and to associate logical names used within JSON content (which is more portable than using physical Java class names).

or also JsonTypeName:

Annotation used for binding logical name that the annotated class has. Used with JsonTypeInfo (and specifically its JsonTypeInfo.use() property) to establish relationship between type names and types.

Basically you need to define a property holding the informations related to the class to be instantiated in the @JsonTypeInfo and define the possible values in @JsonSubTypes

Here is an example

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME, 
  include = As.PROPERTY, 
  property = "type")
@JsonSubTypes({
    @JsonSubTypes.Type(value = Dog.class, name = "dog"),
    @JsonSubTypes.Type(value = Cat.class, name = "cat")
})
public class Animal {
    // The property type is used to know what object instantiate
    private String type;
    ...
}

@JsonTypeName("dog")
public class Dog extends Animal {
    ...
}

@JsonTypeName("cat")
public class Cat extends Animal {
    ...
}

If you don't have such field you need to create a custom deserializer.

An alternative is to convert your records to a list of Map because a Map can hold any valid object.

Top answer
1 of 2
1

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());
      }
   }
}
2 of 2
0

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.

🌐
GitHub
gist.github.com › tyb › 2fc1d7b064b508e10cc7e7fe505fd516
json object deserialization/unmarshalling to java object(mainly array/list) with Jackson · GitHub
json object deserialization/unmarshalling to java object(mainly array/list) with Jackson · Raw · jackson_templates.java · This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.