Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jackson - deserialize inner list of objects to list of one higher level

With Spring Boot and Jackson, how can I deserialize a wrapped/inner list into a list directly in the outer level?

For example, I have:

{
    "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"
            }
        ]
    },
    ...
    }
}

In the JSON, item is a list under items; but I want to parse it as a list named items, directly under transaction, instead of defining a DTO Items which contains a list named item.

Is this possible? How to define this DTO Item?

public class TrasactionDTO {
    private List<Item> items;
    ...
}

public class Item {

}

This question is similar but does not solve the problem. Deserialize wrapped list using Jackson

like image 641
WesternGun Avatar asked Jan 29 '19 15:01

WesternGun


People also ask

What is JsonProperty annotation?

@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.

What is JsonIgnore annotation?

The @JsonIgnore annotation marks a field of a POJO to be ignored by Jackson during serialization and deserialization. Jackson ignores the field both JSON serialization and deserialization. An example of Java class that uses the @JsonIgnore annotation is this.

What does Jackson ObjectMapper do?

The Jackson ObjectMapper can parse JSON from a string, stream or file, and create a Java object or object graph representing the parsed JSON. Parsing JSON into Java objects is also referred to as to deserialize Java objects from JSON. The Jackson ObjectMapper can also create JSON from Java objects.


2 Answers

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
like image 149
Michał Ziober Avatar answered Oct 16 '22 09:10

Michał Ziober


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;
    ...
}
like image 3
WesternGun Avatar answered Oct 16 '22 11:10

WesternGun