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
@JsonProperty is used to mark non-standard getter/setter method to be used with respect to json property.
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.
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.
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 for List
.}
- skip end objectUsing 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:
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;
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With