问题
I'm facing an issue while using ObjectMapper for non-static inner class. I need to create MixIn to make it work but could not reach to the solution. Below is my class(which I can't change) and the MixIn, I tried. Help needed to create such MixIn.
============================
Base Class
public class NestedClass implements Serializable{
private static final long serialVersionUID = -4509619645418618657L;
private NestedInnerClass innerClass;
public NestedClass() {
innerClass = null;
setInnerClass(new NestedInnerClass(new NestedInnerClass2(), new NestedInnerClass3()));
}
public NestedClass(NestedClass nestedCls) {
innerClass = null;
setInnerClass(nestedCls.getInnerClass());
}
public class NestedInnerClass implements Serializable{
private static final long serialVersionUID = 9099474732768960830L;
NestedClass.NestedInnerClass2 nestedInnerClass2;
NestedClass.NestedInnerClass3 nestedInnerClass3;
public NestedInnerClass() {
super();
}
public NestedInnerClass(NestedInnerClass2 nestedInnerClass2, NestedInnerClass3 nestedInnerClass3) {
super();
this.nestedInnerClass2 = nestedInnerClass2;
this.nestedInnerClass3 = nestedInnerClass3;
}
public NestedClass.NestedInnerClass2 getNestedInnerClass2() {
return nestedInnerClass2;
}
public void setNestedInnerClass2(NestedClass.NestedInnerClass2 nestedInnerClass2) {
this.nestedInnerClass2 = nestedInnerClass2;
}
public NestedClass.NestedInnerClass3 getNestedInnerClass3() {
return nestedInnerClass3;
}
public void setNestedInnerClass3(NestedClass.NestedInnerClass3 nestedInnerClass3) {
this.nestedInnerClass3 = nestedInnerClass3;
}
}
public class NestedInnerClass2 implements Serializable{
private static final long serialVersionUID = -3451502802923307744L;
String nestedString;
HashMap<String, String> nestedHashMap = new HashMap<String, String>();
public NestedInnerClass2() {
super();
}
public NestedInnerClass2(String nestedString, HashMap<String, String> nestedHashMap) {
super();
this.nestedString = nestedString;
this.nestedHashMap = nestedHashMap;
}
public NestedInnerClass2(String nestedString) {
this.nestedString = nestedString;
}
public String getNestedString() {
return nestedString;
}
public void setNestedString(String nestedString) {
this.nestedString = nestedString;
}
public HashMap<String, String> getNestedHashMap() {
return nestedHashMap;
}
public void setNestedHashMap(HashMap<String, String> nestedHashMap) {
this.nestedHashMap = nestedHashMap;
}
}
public class NestedInnerClass3 implements Serializable{
private static final long serialVersionUID = 1799737022784300052L;
String nestedString;
public NestedInnerClass3() {
super();
}
public NestedInnerClass3(String nestedString) {
super();
this.nestedString = nestedString;
}
public String getNestedString() {
return nestedString;
}
public void setNestedString(String nestedString) {
this.nestedString = nestedString;
}
}
public NestedInnerClass getInnerClass() {
return innerClass;
}
public void setInnerClass(NestedInnerClass innerClass) {
this.innerClass = innerClass;
}
}
=================================
Child Class of Nested Class :
public class NestedClassChild extends NestedClass implements Serializable, Cloneable{
private static final long serialVersionUID = 7022339501842754692L;
public NestedClassChild() {}
}
=================================
Assist Class :
public class NestedClassAssist {
public static void setNestedValues(NestedClass nestedClass, String key, String value, String nestedString)
{
if(nestedClass != null && nestedClass.getInnerClass() != null && nestedClass.getInnerClass().getNestedInnerClass2() != null)
{
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put(key, value);
nestedClass.getInnerClass().getNestedInnerClass2().setNestedHashMap(hashMap);
nestedClass.getInnerClass().getNestedInnerClass2().setNestedString(nestedString);
}
}
public static void setValue(NestedClass nestedClass, String value){
setNestedValues(nestedClass, "keyStr", value, "ABC");
}
}
=================================
To convert to JSON payload :
public class NestedClassToJson {
public static void main(String[] args) {
NestedClassChild nestedClassChild = new NestedClassChild();
NestedClassAssist.setValue(nestedClassChild, "12345");
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
try {
mapper.writeValue(new File("json/testNested.json"),nestedClassChild);
} catch (Exception e) {
e.printStackTrace();
}
}
}
=================================
Generated JSON payload from above class :
{
"innerClass" : {
"nestedInnerClass2" : {
"nestedString" : "ABC",
"nestedHashMap" : {
"keyStr" : "12345"
}
},
"nestedInnerClass3" : {
"nestedString" : null
}
}
}
=================================
Class to de-serialize from JSON :
public class NestedClassFromJson {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
NestedClass objectNested = mapper.readValue(getPostBodyAsJSON(), NestedClassChild.class);
System.out.println(mapper.writeValueAsString(objectNested));
}
private static String getPostBodyAsJSON() {
StringBuffer postBody = new StringBuffer();
String line = null;
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("json/testNested.json")));
while ((line = reader.readLine()) != null)
postBody.append(line);
} catch (IOException e) {
throw new RuntimeException("Issue Occured While Reading POST Body", e);
}
return postBody.toString();
}
}
=================================
But I'm getting below Exception (though I do have default contructor) :
Exception in thread "main" com.fasterxml.jackson.databind.JsonMappingException: No suitable constructor found for type [simple type, class com.test.jackson.NestedClass$NestedInnerClass]: can not instantiate from JSON object (missing default constructor or creator, or perhaps need to add/enable type information?)
at [Source: { "innerClass" : { "nestedInnerClass2" : { "nestedString" : "ABC", "nestedHashMap" : { "keyStr" : "12345" } }, "nestedInnerClass3" : { "nestedString" : null } }}; line: 1, column: 24] (through reference chain: com.test.jackson.NestedClassChild["innerClass"])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1106)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:296)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:133)
at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:520)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:95)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:258)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:125)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3736)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2726)
at com.test.jackson.NestedClassFromJson.main(NestedClassFromJson.java:21)
=================================
Nested MixIn which I tried but didn't work:
public abstract class NestedMixIn {
@JsonCreator
public NestedMixIn(@JsonProperty("innerClass") NestedInnerClass innerClass ) {
}
public static class SourceIdInnerMixin{
@JsonCreator
public SourceIdInnerMixin(@JsonProperty("nestedInnerClass2") NestedInnerClass2 nestedInnerClass2,
@JsonProperty("nestedInnerClass3") NestedInnerClass3 nestedInnerClass3) {
}
}
}
==========================
If I make the inner classes static , it works but since it is 3rd party class, I can't change it.
Will appreciate your help !!!
回答1:
In your example, I did not notice any relation between parent class and nested classes. Also you mentioned you can change it to static and it works, so all we need to do, is provide an instance of inner class to deserialisation process. By default Jackson
uses com.fasterxml.jackson.databind.deser.BeanDeserializer
to map JSON Object
to a given class. We can extend it and register suppliers to instantiate objects.
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Supplier;
public class JsonNestedApp {
public static void main(String[] args) throws Exception {
File jsonFile = new File("./resource/test.json").getAbsoluteFile();
SimpleModule nestedModule = new SimpleModule();
nestedModule.setDeserializerModifier(new NestedBeanDeserializerModifier());
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(nestedModule);
// other configuration
NestedClass nestedClass = mapper.readValue(jsonFile, NestedClass.class);
System.out.println(nestedClass);
}
}
class NestedBeanDeserializerModifier extends BeanDeserializerModifier {
private final NestedClass parent = new NestedClass();
private final Map<Class, Supplier> availableSuppliers = new HashMap<>();
public NestedBeanDeserializerModifier() {
availableSuppliers.put(NestedClass.NestedInnerClass2.class, () -> parent.new NestedInnerClass2());
availableSuppliers.put(NestedClass.NestedInnerClass3.class, () -> parent.new NestedInnerClass3());
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
final Supplier supplier = availableSuppliers.get(beanDesc.getBeanClass());
if (supplier != null) {
return new NestedBeanDeserializer((BeanDeserializerBase) deserializer, supplier);
}
return deserializer;
}
}
class NestedBeanDeserializer extends BeanDeserializer {
private final Supplier supplier;
protected NestedBeanDeserializer(BeanDeserializerBase src, Supplier supplier) {
super(src);
this.supplier = Objects.requireNonNull(supplier);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(p, ctxt, supplier.get());
}
}
Above code should deserialise JSON
payload with success.
回答2:
As suggested above : Solution is to extend the BeanDeserializer class.
======================
Supplier Interface:
public interface Supplier<T> {
T get();
}
======================
BeanDeserializerModifier :
public class NestedBeanDeserializerModifier extends BeanDeserializerModifier {
private NestedClass parent = new NestedClass();
private Map<Class<?>, Supplier<?>> availableSuppliers = new HashMap<Class<?>, Supplier<?>>();
public NestedBeanDeserializerModifier() {
availableSuppliers.put(NestedClass.NestedInnerClass.class, new Supplier<NestedClass.NestedInnerClass>() {
public NestedClass.NestedInnerClass get() {
return parent.new NestedInnerClass();
}
});
availableSuppliers.put(NestedClass.NestedInnerClass2.class, new Supplier<NestedClass.NestedInnerClass2>() {
public NestedClass.NestedInnerClass2 get() {
return parent.new NestedInnerClass2();
}
});
availableSuppliers.put(NestedClass.NestedInnerClass3.class, new Supplier<NestedClass.NestedInnerClass3>() {
public NestedClass.NestedInnerClass3 get() {
return parent.new NestedInnerClass3();
}
});
}
@Override
public JsonDeserializer<?> modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, JsonDeserializer<?> deserializer) {
final Supplier<?> supplier = availableSuppliers.get(beanDesc.getBeanClass());
if (supplier != null) {
return new NestedBeanDeserializer((BeanDeserializerBase) deserializer, supplier);
}
return deserializer;
}
}
======================
BeanDeserializer :
public class NestedBeanDeserializer extends BeanDeserializer {
private static final long serialVersionUID = 1L;
private Supplier<?> supplier;
protected NestedBeanDeserializer(BeanDeserializerBase src, Supplier<?> supplier) {
super(src);
this.supplier = requireNonNull(supplier);
}
@Override
public Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
return super.deserialize(p, ctxt, supplier.get());
}
private static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
}
====================
Output class using above deserializer :
public class NestedClassFromJson {
public static void main(String[] args) throws JsonParseException, JsonMappingException, IOException {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.writerWithDefaultPrettyPrinter();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
SimpleModule nestedModule = new SimpleModule();
nestedModule.setDeserializerModifier(new NestedBeanDeserializerModifier());
mapper.registerModule(nestedModule);
NestedClass objectNested = mapper.readValue(getPostBodyAsJSON(), NestedClassChild.class);
System.out.println(mapper.writeValueAsString(objectNested));
}
private static String getPostBodyAsJSON() {
StringBuffer postBody = new StringBuffer();
String line = null;
try {
BufferedReader reader = new BufferedReader(new FileReader(new File("json/testNested.json")));
while ((line = reader.readLine()) != null)
postBody.append(line);
} catch (IOException e) {
throw new RuntimeException("Issue Occured While Reading POST Body", e);
}
return postBody.toString();
}
}
来源:https://stackoverflow.com/questions/61532101/need-mixin-resolution-for-non-static-inner-class-using-objectmapper-java-6