I am looking at different ways to workaround the fact that @JsonCreator does 
not 
"inherit" in the class hierarchy. I have tried different things, but have 
failed to make it work without an ugly hack. The best I've come up with is 
to use the Module.modifyDeserializer() method to modify or replace the new 
deserializer. I need to replace the _propertyBasedCreator of this instance 
but cannot find a way other than reflection surgery. See test code below.

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanDescription;
import com.fasterxml.jackson.databind.DeserializationConfig;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.InjectableValues;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.BeanDeserializerBase;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.deser.ValueInstantiator;
import com.fasterxml.jackson.databind.deser.impl.BeanPropertyMap;
import com.fasterxml.jackson.databind.deser.impl.PropertyBasedCreator;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;

public class Test_JsonCreator_Via_Deserializer {

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = 
JsonTypeInfo.As.PROPERTY, property = "@class", visible = true)
@JsonIgnoreProperties({ "@class" })
public static class TestDomain  {
private transient Object constructorAttr;
public String fieldAttr;

public TestDomain(Object consAttr) {
this.constructorAttr = consAttr;
}

@Override public String toString() {
return getClass().getName() + " " + constructorAttr + " " + fieldAttr;
}
}
public static class TestDomainSub extends TestDomain {
public TestDomainSub(Object consAttr) {
super(consAttr);
}
}
public static class TestModule extends SimpleModule {
public TestModule() {
super("TestDomain", new Version(1, 0, 0, null, null, null));
}

@Override public void setupModule(SetupContext context) {
// addValueInstantiator(TestDomain.class, new 
ValueInstantiatorClassTestDomain(TestDomain,class));
// addValueInstantiator(TestDomainSub.class, new 
ValueInstantiatorClassTestDomain(TestDomainSub,class));;
// ... and many many more ... this is manual and brittle ... no thanks

setDeserializerModifier(new BeanDeserializerModifier() {

@Override public JsonDeserializer<?> 
modifyDeserializer(DeserializationConfig config, BeanDescription beanDesc, 
JsonDeserializer<?> deserializer) {
if (TestDomain.class.isAssignableFrom(beanDesc.getBeanClass())) {

// would have been nice to add ValueInstantiator here but it is too late
// _valueInstantiators = new SimpleValueInstantiators();
// _valueInstantiators = 
_valueInstantiators.addValueInstantiator(beanDesc.getBeanClass(), new 
MyValueInstantiator(beanDesc.getBeanClass()));
// context.addValueInstantiators(_valueInstantiators); //already moved on

try {
ValueInstantiator newValueInstantiator = new 
MyValueInstantiator(beanDesc.getBeanClass());

// This is the ugly hack that works, but ugh
PropertyBasedCreator pbc = PropertyBasedCreator.construct(null, 
newValueInstantiator, new SettableBeanProperty[0], 
BeanPropertyMap.construct(new ArrayList(0), false, new HashMap()));
Field fields = 
BeanDeserializerBase.class.getDeclaredField("_propertyBasedCreator");
makeFieldAccessible(fields);
fields.set(deserializer, pbc);

} catch (Exception e) {
System.err.println(e);
}
}
return deserializer;
}
});

super.setupModule(context);
}
static void makeFieldAccessible(Field field) {
try {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
} catch (Exception e) {
e.printStackTrace();
}

}
}

static class MyValueInstantiator extends ValueInstantiator {

Class klass;

public MyValueInstantiator(Class klass) {
this.klass = klass;
}
@Override public Object createFromObjectWith(DeserializationContext ctxt, 
Object[] args) throws IOException {
try {
Constructor constructor = klass.getConstructor(new Class[] { Object.class 
});
Object externalInjected = ctxt.findInjectableValue("constructorAttr", null, 
null);
return constructor.newInstance(new Object[] { externalInjected });
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

public static void main(String[] args) throws IOException {

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(new TestModule());

Object externalInjected = "constructor-field-value";
InjectableValues.Std injectableValues = new InjectableValues.Std();
injectableValues.addValue("constructorAttr", externalInjected);
objectMapper.setInjectableValues(injectableValues);

TestDomain td = new TestDomain(externalInjected);
td.fieldAttr = "field-attr-1";
String json = objectMapper.writeValueAsString(td);
System.out.println(json);
// 
{"@class":"dataswarm.service.channel.input.Test_JsonCreator_Via_Deserializer$TestDomain","fieldAttr":"field-attr-1"}

TestDomain td2 = objectMapper.readValue(json, TestDomain.class);
System.out.println(td2);
// 
dataswarm.service.channel.input.Test_JsonCreator_Via_Deserializer$TestDomain 
constructor-field-value field-attr-1

TestDomainSub sub = new TestDomainSub(externalInjected);
sub.fieldAttr = "field-attr-2";
json = objectMapper.writeValueAsString(sub);
System.out.println(json);
//{"@class":"dataswarm.service.channel.input.Test_JsonCreator_Via_Deserializer$TestDomainSub","fieldAttr":"field-attr-2"}

TestDomain sub2 = objectMapper.readValue(json, TestDomain.class);
System.out.println(sub2);
// 
dataswarm.service.channel.input.Test_JsonCreator_Via_Deserializer$TestDomainSub 
constructor-field-value field-attr-2

}



 

Another way I tried was to create a custom Deserializer that would read the 
"@class" attribute and handle instantiation. But I could never figure out 
how to make a StdDeserializer handle instantiation then delegate everything 
else to the "normal" deserialization. Something like the code below:


// unused. this did not work

static class OverrideInstantiationDeserializer<T extends TestDomain> 
extends StdDeserializer<T> {

private JsonDeserializer<T> remainderDeserialization;

public OverrideInstantiationDeserializer(Class<T> beanClass, 
JsonDeserializer<T> deserializer) {
super(beanClass);
this.remainderDeserialization = deserializer;
}

@Override public boolean isCachable() {
return true;
}

@Override public T deserialize(JsonParser p, DeserializationContext ctxt) 
throws IOException, JsonProcessingException {
try {
Constructor constructor = handledType().getConstructor(new Class[] { 
Object.class });
Object externalInjected = ctxt.findInjectableValue("constructorAttr", null, 
null);
T inst = (T) constructor.newInstance(new Object[] { externalInjected });
// NOW WHAT?
// have to ignore the "@class" property
// super.deserialize(p, ctxt, inst); //Nope. This calls back here
// return remainderDeserialization.deserialize(p, ctxt, inst); //Nope. No 
_valueDeserializer assigned
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
}


 

Beyond the original post introducing ValueInstantiator 
<http://www.cowtowncoder.com/blog/archives/2011/10/entry_463.html> , I have 
found no example code or tutorials. The unit test code helps understanding, 
but didn't have a solution to my problem. It did give me hope that I could 
implement the SimpleModule as follows:


public static class TestModule extends SimpleModule { public TestModule() { 
super("TestDomain", new Version(1, 0, 0, null, null, null)); } @Override 
public void setupModule(SetupContext context) { setValueInstantiators(new 
ClassHierarchyLookupValueInstantiators()); super.setupModule(context); } } 
static class ClassHierarchyLookupValueInstantiators extends 
SimpleValueInstantiators { private static final long serialVersionUID = 1L; 
//lazy ValueInstantiator @Override public ValueInstantiator 
findValueInstantiator(DeserializationConfig config, BeanDescription 
beanDesc, ValueInstantiator defaultInstantiator) { if 
(TestDomain.class.isAssignableFrom(beanDesc.getBeanClass())) { ClassKey 
classkey = new ClassKey(beanDesc.getBeanClass()); ValueInstantiator inst = 
_classMappings.get(classkey); if (inst == null) { inst = new 
MyValueInstantiator(beanDesc.getBeanClass()); _classMappings.put(classkey, 
inst); } return inst; } return defaultInstantiator; } }

While the above seemed like it would work, when I followed the code to 
BeanDeserializerBase.deserializeFromObjectUsingNonDefault(JsonParser p, 
DeserializationContext ctxt), it failed because delegateDeser was null, 
i.e.,:


    protected Object deserializeFromObjectUsingNonDefault(JsonParser p,
            DeserializationContext ctxt) throws IOException
    {
        final JsonDeserializer<Object> delegateDeser = 
_delegateDeserializer();
        if (delegateDeser != null) {
//NEEDED TO GET HERE, BUT delegateDeser  IS NULL
            return _valueInstantiator.createUsingDelegate(ctxt,
                    delegateDeser.deserialize(p, ctxt));
        }
//AND THIS IS NULL
        if (_propertyBasedCreator != null) {
            return _deserializeUsingPropertyBased(p, ctxt);
        }
//WE GET HERE AND IT IS TOO LATE
        // 25-Jan-2017, tatu: We do not actually support use of Creators 
for non-static
        //   inner classes -- with one and only one exception; that of 
default constructor!
        //   -- so let's indicate it
        Class<?> raw = _beanType.getRawClass();
        if (ClassUtil.isNonStaticInnerClass(raw)) {
            return ctxt.handleMissingInstantiator(raw, null, p,
"can only instantiate non-static inner class by using default, no-argument 
constructor");
        }
        return ctxt.handleMissingInstantiator(raw, getValueInstantiator(), 
p,
                "cannot deserialize from Object value (no delegate- or 
property-based Creator)");
    }



Anyone got any better ideas? TIA.


-- 
You received this message because you are subscribed to the Google Groups 
"jackson-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to