58
{ vendors: [ { vendor: { id: 367, name: "Kuhn-Pollich", company_id: 1, } }, { vendor: { id: 374, name: "Sawayn-Hermann", company_id: 1, } }] } 

I have a Vendor object that can properly be deserialized from a single "vendor" json, but I want to deserialize this into a Vendor[], I just can't figure out how to make Jackson cooperate. Any tips?

4
  • 7
    This is invalid JSON. vendors has as value an array, which has a single object, and the single object has a 'vendor' property, which is followed by a bare top-level opject. i.e. the second vendor object has no associated property in the single object that is in the array. Furthermore, the property names aren't strings, they need to be quoted in JSON. I'm guessing that you've typed the JSON in wrong? A good answer will depend on knowing what kind of JSOn you're actually working with. Commented Jul 31, 2012 at 19:24
  • Sorry, let me correct the JSON -- Should be fixed now Commented Jul 31, 2012 at 19:50
  • You're not able (or don't want) to have a Vendors class that contains a List<Vendor>? Commented Jul 31, 2012 at 20:05
  • I am but the issue is the fact that the Vendor object is a nested as the "vendor" property of each object in the array, rather than being the object itself. This means I'd have to have a Vendors class with a list of VendorWrapper, where each VendorWrapper contains a single Vendor. I have this setup now, but it's less than ideal. Commented Aug 1, 2012 at 4:09

4 Answers 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!

Sign up to request clarification or add additional context in comments.

3 Comments

+1 for mapIntoObject(JsonNode)... so if you want to use readValue with a JsonNode just pass its traverse()
Works well: one minor simplification I'd suggest is ObjectMapper.convertValue(), which can be used to replace 3 lines in mapIntoObject: return mapper.convertValue(node, wrappedType);
@Patrick, I tried running this example code. getting NPE in SkipWrapperObjectDeserializer at wrappedType = collectedType.getRawClass(); in createContextual() method. Can you please help me to fix this. Thanks.
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.

2 Comments

Thank you, this is how I was doing it I just hoped there was a better way. I'll mark it as correct.
I'd also like to see a solution using something like UNWRAP_ROOT_VALUES, only 1 level deeper, but I don't think it's possible. Another option of course is to use a custom deserializer, and just add hooks to look for the actual objects you're interested in and discard everything else, or to use the Jackson Tree Model approach, throw away the top-level JsonNode, and take the JsonNodes that wrap your Vendors, call getTextValue, and pass that to mapper.readValue. Jackson really gives you a surfeit of options.
14

@Patrick I would improve your solution a bit

@Override public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException { ObjectNode objectNode = jp.readValueAsTree(); JsonNode wrapped = objectNode.get(wrapperKey); JsonParser parser = node.traverse(); parser.setCodec(jp.getCodec()); Vendor mapped = parser.readValueAs(Vendor.class); return mapped; } 

It works faster :)

Comments

0

I'm quite late to the party, but one approach is to use a static inner class to unwrap values:

import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; class Scratch { private final String aString; private final String bString; private final String cString; private final static String jsonString; static { jsonString = "{\n" + " \"wrap\" : {\n" + " \"A\": \"foo\",\n" + " \"B\": \"bar\",\n" + " \"C\": \"baz\"\n" + " }\n" + "}"; } @JsonCreator Scratch(@JsonProperty("A") String aString, @JsonProperty("B") String bString, @JsonProperty("C") String cString) { this.aString = aString; this.bString = bString; this.cString = cString; } @Override public String toString() { return "Scratch{" + "aString='" + aString + '\'' + ", bString='" + bString + '\'' + ", cString='" + cString + '\'' + '}'; } public static class JsonDeserializer { private final Scratch scratch; @JsonCreator public JsonDeserializer(@JsonProperty("wrap") Scratch scratch) { this.scratch = scratch; } public Scratch getScratch() { return scratch; } } public static void main(String[] args) throws JsonProcessingException { ObjectMapper objectMapper = new ObjectMapper(); Scratch scratch = objectMapper.readValue(jsonString, Scratch.JsonDeserializer.class).getScratch(); System.out.println(scratch.toString()); } } 

However, it's probably easier to use objectMapper.configure(SerializationConfig.Feature.UNWRAP_ROOT_VALUE, true); in conjunction with @JsonRootName("aName"), as pointed out by pb2q

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.