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.
