First: `ValueInstantiator` is not well-documented, or even well thought-out
abstraction unfortunately.
So I am not surprised that finding solution through that route is
challenging.

But what would be helpful would be to give a bit more complete example of
what you try to achieve: not so how, but what. @JsonCreator on static
methods would be seen through inheritance, but one on constructor not --
this because it would not make much sense (instance of super class can not
be substituted for sub class). So I am not sure what the goal here is.

I am also interested in references to @JsonTypeInfo; if it's used for
polymorphic deserialization that makes sense, but it won't work well to add
other extra info (that is, internal handling is modified quite drastically
by its existence).

-+ Tatu +-

On Sun, Jan 20, 2019 at 9:38 AM Rich MacDonald <[email protected]>
wrote:

> 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.
>

-- 
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