Here is a rough but more declarative solution. I haven't been able to get it down to a single annotation, but this seems to work well. Also not sure about performance on large data sets.

Given this JSON:

{
    "list": [
        {
            "wrapper": {
                "name": "Jack"
            }
        },
        {
            "wrapper": {
                "name": "Jane"
            }
        }
    ]
}

And these model objects:

public class RootObject {
    @JsonProperty("list")
    @JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class)
    @SkipWrapperObject("wrapper")
    public InnerObject[] innerObjects;
}

and

public class InnerObject {
    @JsonProperty("name")
    public String name;
}

Where the Jackson voodoo is implemented like:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface SkipWrapperObject {
    String value();
}

and

public class SkipWrapperObjectDeserializer extends JsonDeserializer<Object> implements
        ContextualDeserializer {
    private Class<?> wrappedType;
    private String wrapperKey;

    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException {
        SkipWrapperObject skipWrapperObject = property
                .getAnnotation(SkipWrapperObject.class);
        wrapperKey = skipWrapperObject.value();
        JavaType collectionType = property.getType();
        JavaType collectedType = collectionType.containedType(0);
        wrappedType = collectedType.getRawClass();
        return this;
    }

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode objectNode = mapper.readTree(parser);
        JsonNode wrapped = objectNode.get(wrapperKey);
        Object mapped = mapIntoObject(wrapped);
        return mapped;
    }

    private Object mapIntoObject(JsonNode node) throws IOException,
            JsonProcessingException {
        JsonParser parser = node.traverse();
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(parser, wrappedType);
    }
}

Hope this is useful to someone!

Answer from Patrick on Stack Overflow
🌐
Baeldung
baeldung.com › home › json › jackson › mapping nested values with jackson
Mapping Nested Values with Jackson | Baeldung
January 8, 2024 - Learn three ways to deserialize nested JSON values in Java using the Jackson library.
Top answer
1 of 4
37

Here is a rough but more declarative solution. I haven't been able to get it down to a single annotation, but this seems to work well. Also not sure about performance on large data sets.

Given this JSON:

{
    "list": [
        {
            "wrapper": {
                "name": "Jack"
            }
        },
        {
            "wrapper": {
                "name": "Jane"
            }
        }
    ]
}

And these model objects:

public class RootObject {
    @JsonProperty("list")
    @JsonDeserialize(contentUsing = SkipWrapperObjectDeserializer.class)
    @SkipWrapperObject("wrapper")
    public InnerObject[] innerObjects;
}

and

public class InnerObject {
    @JsonProperty("name")
    public String name;
}

Where the Jackson voodoo is implemented like:

@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotation
public @interface SkipWrapperObject {
    String value();
}

and

public class SkipWrapperObjectDeserializer extends JsonDeserializer<Object> implements
        ContextualDeserializer {
    private Class<?> wrappedType;
    private String wrapperKey;

    public JsonDeserializer<?> createContextual(DeserializationContext ctxt,
            BeanProperty property) throws JsonMappingException {
        SkipWrapperObject skipWrapperObject = property
                .getAnnotation(SkipWrapperObject.class);
        wrapperKey = skipWrapperObject.value();
        JavaType collectionType = property.getType();
        JavaType collectedType = collectionType.containedType(0);
        wrappedType = collectedType.getRawClass();
        return this;
    }

    @Override
    public Object deserialize(JsonParser parser, DeserializationContext ctxt)
            throws IOException, JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        ObjectNode objectNode = mapper.readTree(parser);
        JsonNode wrapped = objectNode.get(wrapperKey);
        Object mapped = mapIntoObject(wrapped);
        return mapped;
    }

    private Object mapIntoObject(JsonNode node) throws IOException,
            JsonProcessingException {
        JsonParser parser = node.traverse();
        ObjectMapper mapper = new ObjectMapper();
        return mapper.readValue(parser, wrappedType);
    }
}

Hope this is useful to someone!

2 of 4
31

Your data is problematic in that you have inner wrapper objects in your array. Presumably your Vendor object is designed to handle id, name, company_id, but each of those multiple objects are also wrapped in an object with a single property vendor.

I'm assuming that you're using the Jackson Data Binding model.

If so then there are two things to consider:

The first is using a special Jackson config property. Jackson - since 1.9 I believe, this may not be available if you're using an old version of Jackson - provides UNWRAP_ROOT_VALUE. It's designed for cases where your results are wrapped in a top-level single-property object that you want to discard.

So, play around with:

objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true);

The second is using wrapper objects. Even after discarding the outer wrapper object you still have the problem of your Vendor objects being wrapped in a single-property object. Use a wrapper to get around this:

class VendorWrapper
{
    Vendor vendor;

    // gettors, settors for vendor if you need them
}

Similarly, instead of using UNWRAP_ROOT_VALUES, you could also define a wrapper class to handle the outer object. Assuming that you have correct Vendor, VendorWrapper object, you can define:

class VendorsWrapper
{
    List<VendorWrapper> vendors = new ArrayList<VendorWrapper>();

    // gettors, settors for vendors if you need them
}

// in your deserialization code:
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.readValue(jsonInput, VendorsWrapper.class); 

The object tree for VendorsWrapper is analogous to your JSON:

VendorsWrapper:
    vendors:
    [
        VendorWrapper
            vendor: Vendor,
        VendorWrapper:
            vendor: Vendor,
        ...
    ]

Finally, you might use the Jackson Tree Model to parse this into JsonNodes, discarding the outer node, and for each JsonNode in the ArrayNode, calling:

mapper.readValue(node.get("vendor").getTextValue(), Vendor.class);

That might result in less code, but it seems no less clumsy than using two wrappers.

🌐
GitHub
github.com › spring-projects-experimental › spring-native › issues › 1569
jackson can't serialize nested object · Issue #1569 · spring-attic/spring-native
April 2, 2022 - public class BaseResponse { private int errno; private String errmsg; private Object data; public BaseResponse(int errno, String errmsg, Object data) { this.errno = errno; this.errmsg = errmsg; this.data = data; } public BaseResponse() { } public int getErrno() { return errno; } public void setErrno(int errno) { this.errno = errno; } public String getErrmsg() { return errmsg; } public void setErrmsg(String errmsg) { this.errmsg = errmsg; } public Object getData() { return data; } public void setData(Object data) { this.data = data; } }
Author   zhaozhi406
🌐
Syntax Savvy
learn.syntaxsavvy.dev › langs › tools › jackson › advanced_jackson_usage › using_jackson_with_nested_objects
Using Jackson with nested objects | Syntax Savvy
To serialize and deserialize nested objects with Jackson, we follow the same basic steps as we do for non-nested objects. We create a ObjectMapper instance and use its methods to serialize and deserialize the object.
🌐
Stack Overflow
stackoverflow.com › questions › 30768836 › jackson-how-to-serialize-nested-object-with-custom-object-names
json - jackson - how to serialize nested object with custom object names? - Stack Overflow
class Person { private String name; private Address addressGiven; public void setName(String name) { this.name = name; } public void setAddressGiven(Address addressGiven) { this.addressGiven = addressGiven; } public String getName() { return name; } public Address getAddressGiven() { return addressGiven; } enum AddressType { HOME, OFFICE, BUSINESS, DEFAULT } static class Address { @JsonProperty("stno") private String streetNo; @JsonProperty("type") private AddressType addType; public String getStreetNo() { return streetNo; } public void setStreetNo(String streetNo) { this.streetNo = streetNo;
🌐
Scala Users
users.scala-lang.org › question
Serialization for Nested Polymorphic Types with FasterXML/Jackson - Question - Scala Users
April 26, 2020 - Hi there, I got into a serialization issue with nested polymorphic types as described below. I have polymorphic types defined as below: import com.fasterxml.jackson.annotation._ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT) class PyContextMetaType case class Wpbp (sepInd: Int , esgGrping: Int , psaGrping: Int , pernr: BigInt , eeGrp: Char , eeSubGrp: String ...
🌐
Medium
medium.com › swlh › fasterxml-jackson-tips-for-json-in-java-3a910e846115
FasterXML/jackson Tips for JSON in Java | by Kamer Elciyar | The Startup | Medium
March 14, 2020 - Q2: How to use different field names in POJO to map JSON? Q3: How to deserialize nested JSON objects? Q4: How to create a custom deserializer? Q5: How to deserialize enum values? ... This article is not a zero-to-hero-like article. So, I will not start from basics but introduce Jackson with a few words.
Find elsewhere
🌐
Restack
restack.io › p › open-source-ai-embedding-solutions-answer-jackson-json-serialize-nested-objects-cat-ai
Jackson Json Serialize Nested Objects | Restackio
December 30, 2022 - This example demonstrates how to convert a JSON string back into a Flight object, preserving the nested structure. Jackson's ability to serialize and deserialize nested objects makes it an invaluable tool for developers working with complex data structures.
🌐
GitHub
github.com › FasterXML › jackson-databind › discussions › 3925
Using ```@JsonProperty``` during serialization on nested records interferes with the default property order · FasterXML/jackson-databind · Discussion #3925
Using nested records changes the order of the properties in the serialized output. Here's a simple example: public record NestedRecordOne( String id, String email, @JsonProperty(value = "yikes!") NestedRecordTwo nestedRecordTwo) {} public record NestedRecordTwo ( String id, String passport) {} public static void main(String[] args) throws IOException { ObjectMapper objectMapper = new ObjectMapper(); NestedRecordTwo nestedRecordTwo = new NestedRecordTwo("2", "111110"); NestedRecordOne nestedRecordOne = new NestedRecordOne("1", "test@records.com", nestedRecordTwo); objectMapper.writeValue(new File("nested-record.json"), nestedRecordOne); }
Author   FasterXML
🌐
Stack Overflow
stackoverflow.com › questions › 55418043 › serialize-nested-objects-with-jackson
java - Serialize nested objects with jackson - Stack Overflow
April 3, 2023 - Jackson: Serialization to JSON on Objects using nested Objects, merge child fields into owning class
🌐
GitHub
github.com › FasterXML › jackson-databind › issues › 2640
wrapping the nested object into single-property JSON Object + using @JsonRootName · Issue #2640 · FasterXML/jackson-databind
March 3, 2020 - @JsonRootName("container") class Conatiner { String conatinerProp; } @JsonRootName("Strip") class Strip { String stripProp; } class Presentation { @JsonProperty("layout") List<Object> layout ; } class MainApp { Presentation presentation = new Presentation(); Conatiner c1 = new Conatiner("c1"); Strip s1= new Strip("s1"); List<Object> layout = new ArrayList<>(); layout .add(c1); layout .add(c2); layout .add(s1); layout .add(s2); presentation.setLayout( layout ); ObjectMapper mapper = new ObjectMapper(); ObjectWriter writer = mapper.writer().with(SerializationFeature.WRAP_ROOT_VALUE); String responseStr = writer.writeValueAsString(presentation); }
Author   gr423
🌐
Mosy
thepracticaldeveloper.com › java-and-json-jackson-serialization-with-objectmapper
Java and JSON – Jackson Serialization with ObjectMapper | Mosy
December 13, 2023 - The Map values are serialized using the default serializers unless you override them. That means you can also set Maps as values if you want nested JSON objects, or you can use Java objects that are serialized with the per-field strategy as usual.
🌐
Stack Overflow
stackoverflow.com › questions › 49994799 › serialization-of-nested-classes-using-jackson
java - Serialization of nested classes using Jackson - Stack Overflow
February 27, 2023 - Jackson: Serialization to JSON on Objects using nested Objects, merge child fields into owning class
🌐
Stack Overflow
stackoverflow.com › questions › 56418361 › deserialize-json-with-list-of-nested-objects › 56422254
java - Deserialize json with list of nested objects - Stack Overflow
Assuming Class1 has fields prop1 and prop2, and NestedProp has fields p1 and p2, what class has field nestedProp? ... You have two arrays in your JSON payloads. So, you need to create extra POJO for it. See below example: import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; 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(); Root root = mapper.readValue(jsonFile, Root.class); System.out.println(root); } } class Roo
Top answer
1 of 2
3

It can be done with custom serializer:

class EscapedJsonSerializer extends StdSerializer<Object> {
    public EscapedJsonSerializer() {
        super((Class<Object>) null);
    }


    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
        StringWriter str = new StringWriter();
        JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
        if (value instanceof Collection || value.getClass().isArray()) {
            tempGen.writeStartArray();
            if (value instanceof Collection) {
                for (Object it : (Collection) value) {
                    writeTree(gen, it, tempGen);
                }
            } else if (value.getClass().isArray()) {
                for (Object it : (Object[]) value) {
                    writeTree(gen, it, tempGen);
                }
            }
            tempGen.writeEndArray();
        } else {
            provider.defaultSerializeValue(value, tempGen);
        }
        tempGen.flush();
        gen.writeString(str.toString());
    }


    @Override
    public void serializeWithType(Object value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
        StringWriter str = new StringWriter();
        JsonGenerator tempGen = new JsonFactory().setCodec(gen.getCodec()).createGenerator(str);
        writeTree(gen, value, tempGen);
        tempGen.flush();
        gen.writeString(str.toString());
    }

    private void writeTree(JsonGenerator gen, Object it, JsonGenerator tempGen) throws IOException {
        ObjectNode tree = ((ObjectMapper) gen.getCodec()).valueToTree(it);
        tree.set("@class", new TextNode(it.getClass().getName()));
        tempGen.writeTree(tree);
    }
}

and deserializer:

class EscapedJsonDeserializer extends JsonDeserializer<Object> implements ContextualDeserializer {
    private final Map<JavaType, JsonDeserializer<Object>> cachedDeserializers = new HashMap<>();

    @Override
    public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        throw new IllegalArgumentException("EscapedJsonDeserializer should delegate deserialization for concrete class");

    }

    @Override
    public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) throws JsonMappingException {
        JavaType type = (ctxt.getContextualType() != null) ?
                ctxt.getContextualType() : property.getMember().getType();
        return cachedDeserializers.computeIfAbsent(type, (a) -> new InnerDeserializer(type));
    }

    private class InnerDeserializer extends JsonDeserializer<Object> {
        private final JavaType javaType;

        private InnerDeserializer(JavaType javaType) {
            this.javaType = javaType;
        }

        @Override
        public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
            String string = p.readValueAs(String.class);
            return ((ObjectMapper) p.getCodec()).readValue(string, javaType);
        }

        @Override
        public Object deserializeWithType(JsonParser p, DeserializationContext ctxt, TypeDeserializer typeDeserializer)
                throws IOException {

            String str = p.readValueAs(String.class);


            TreeNode root = ((ObjectMapper) p.getCodec()).readTree(str);
            Class clz;
            try {
                clz = Class.forName(((TextNode) root.get("@class")).asText());
                Object newJsonNode = p.getCodec().treeToValue(root, clz);
                return newJsonNode;
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

The field should be annotated with @JsonSerialize and @JsonDeserialize (if needed)

class Outer {
    @JsonTypeInfo(include = JsonTypeInfo.As.PROPERTY, use = JsonTypeInfo.Id.CLASS)
    @JsonSerialize(using = EscapedJsonSerializer.class)
    @JsonDeserialize(using = EscapedJsonDeserializer.class)
    public Foo val;
}

It works well with simple collections (list, arrays) and to some extent with polymorphism, although more elaborate solution may be needed for specific polymorphism related issues. Example output looks like this:

{"val":"{\"foo\":\"foo\",\"@class\":\"org.test.Foo\"}"}
{"val":"{\"foo\":\"foo\",\"bar\":\"bar\",\"@class\":\"org.test.Bar\"}"}
2 of 2
0

I also couldn't find built-in solution and ended up writing custom converter:

public class ObjectToJsonStringConverter extends StdConverter<Object, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convert(Object value) {
        try {
            return objectMapper.writeValueAsString(value);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException(e);
        }
    }
}

usage:

@Value
private static class Message {
    private final String type;

    @JsonSerialize(converter = ObjectToJsonStringConverter.class)
    private final MyType message;
}
🌐
QA Automation Expert
qaautomation.expert › 2021 › 11 › 08 › how-to-create-nested-json-object-using-jackson-api
How to create Nested JSON Object using POJO – Jackson API
August 27, 2025 - In this tutorial, I will explain the creation of a nested JSON Object (JSON with multiple nodes) using POJO. It is recommended to go through these tutorials to understand POJO, JSON Object, and JSON Array. How to create JSON Object Payload using POJO – Jackson API · How to create JSON Array Payload using POJO – Jackson API · We are using Jackson API for Serialization and Deserialization.