Jackson JSON has no problem serializing/deserializing this class:
public class MyClass {
public class Nested {
public String string;
public Nested() {}
}
public Nested nestedVar;
}
but on this one:
public class MyClass {
class Nested {
public String string;
public Nested() {}
}
public Nested nestedVar;
public List nestedList;
}
I get this exception when deserializing:
com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class test.MyClass$Nested]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: java.io.StringReader@26653222; line: 1, column: 48] (through reference chain: test.MyClass["nestedList"]->java.util.ArrayList[0])
In the first case, Jackson has no problem dealing with an instance of a nested class, but not in the second case.
Must I write a custom deserializer?
Test code (Jackson 2.6.3):
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
public class ATest {
public static void main(String[] args) throws IOException {
ObjectMapper mapper = new ObjectMapper();
StringWriter sw = new StringWriter();
MyClass myClass = new MyClass();
MyClass.Nested nestedVar = myClass.new Nested();
List nestedList = new ArrayList<>();
nestedList.add(nestedVar);
myClass.nestedList =nestedList;
myClass.nestedVar = nestedVar;
mapper.writeValue(sw, myClass);
System.out.println(sw.toString());
StringReader sr = new StringReader(sw.toString());
MyClass z = mapper.readValue(sr, MyClass.class);
}
}
解决方案
Looks like the recognition of non-static inner classes is done where they are properties directly on their containing bean (BeanDeserializerBase.java line 476 in 2.6.3). So an intervening Collection deserializer would go past that. A custom deserializer is likely the simplest option here.
Note that you can still use Jackson to read the properties of Nested, and just implement the construction of it yourself, in a custom deserializer only used when deserializing a list of Nested objects.
To do this, annotate the list like so:
@JsonDeserialize(contentUsing = NestedDeserializer.class)
public List nestedList;
and then use a custom deserializer that will:
Look at the parsing context when called to find the containing MyClass instance.
Encapsulate a default/root-level deserializer of Nested to delegate the work of deserializing the content to.
For example:
public static final class NestedDeserializer extends StdDeserializer
implements ResolvableDeserializer {
private JsonDeserializer underlyingDeserializer;
public NestedDeserializer() {
super(MyClass.Nested.class);
}
@Override
public void resolve(DeserializationContext ctxt) throws JsonMappingException {
underlyingDeserializer = ctxt
.findRootValueDeserializer(ctxt.getTypeFactory().constructType(MyClass.Nested.class));
}
@Override
public Nested deserialize(JsonParser p, DeserializationContext ctxt)
throws IOException, JsonProcessingException {
JsonStreamContext ourContext = p.getParsingContext();
JsonStreamContext listContext = ourContext.getParent();
JsonStreamContext containerContext = listContext.getParent();
MyClass container = (MyClass) containerContext.getCurrentValue();
MyClass.Nested value = container.new Nested();
// note use of three-argument deserialize method to specify instance to populate
underlyingDeserializer.deserialize(p, ctxt, value);
return value;
}
}