Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Give Custom Deserializers access to the resolved target Class of the currently deserialized object #165

Closed
Laures opened this issue Feb 12, 2013 · 21 comments
Milestone

Comments

@Laures
Copy link

Laures commented Feb 12, 2013

Jackson resolves the target class for a json object using parameter/property types, annotations, json properties or a parameter in the object mapper.

please give custom deserializers easy access to this information. currently it is nearly impossible to create a deserializer that works for multiple types. the only way would be to implement "contextualdeserializer" and access the bean property which is very complicated.

@cowtowncoder
Copy link
Member

Can you suggest a way in which it would make sense to pass this information? Keep in mind that deserializers are stateless objects, so this information would need to be passed in context somehow; and should not incur significant additional overhead for cases where it is not needed.

Another way of putting my question is whether this would be during construction of deserializer (and it being cached), or during operation. Jackson tries to put as much work as possible on former part, FWIW.

@Laures
Copy link
Author

Laures commented Feb 13, 2013

i would suggest an interface similar to the ContextualDeserializer that a deserializer/serializer can implement and return a properly configured instance.

@cowtowncoder
Copy link
Member

So something like "TypeSpecificDeserializer" add-on interface? So compared to ContextualDeserializer, type for which deserializer is being built would be explicitly passed (and in case of root values, it wouldn't even be available).

I also assume this should be resolved before ContextualDeserializer, so type-specific specialization would take place first, and then one could also implement ContextualDeserializer if that makes sense.
Further, this resolution could be done before caching and only be done once per type, whereas ContextualDeserializer is called once per property.

I think that could work...

@cowtowncoder
Copy link
Member

Oh, one more question -- wouldn't it be possible to configure this on your end when adding custom deserializers? Although SimpleModule only supports dumb registration by specific type, underlying Deserializers (and matching Serializers) interfaces pass in specific type for which deserializer is being used. So why does this not work?

@Laures
Copy link
Author

Laures commented Feb 15, 2013

could you elaborate how they do that? afaik a deserialize call does not contain type information.

@cowtowncoder
Copy link
Member

I meant com.fasterxml.jackson.databind.deser .Deserializers (note the 's') abstraction that gets the callback to construct a new JsonDeserializer, "findBeanDeserializer(...)".
Not during deserialization itself (that information is not pass and deserializer is expected to have retained it from construction).

@Laures
Copy link
Author

Laures commented Feb 17, 2013

Ah. That works fine for (de)serializers that handle a specific type. but what about (de)serializers that handle a specific property, maybe of an (for them) unknown type? the type has to be known to jackson at (de)serialization but there is no way for such a (de)serializer to know it. over the last weeks i had several such cases. it is possible to work around that with the contextual(de)serializer interface. but that workaround misses information and therefor can fail in special scenarios.

@cowtowncoder
Copy link
Member

That's not how Jackson deserializers work -- they are bound to specific types. You can of course register single (de)serializer for all kinds of types, but if so, you will have to deal pass through the type and contextual information as it becomes available, creating distinct deserializer instances with that information (as necessary). So you sort of need to build more distinct instances incrementally, such that deserializer will know what kind of type it will be dealing with as part of construction process. This information will not be passed during actual deserialization calls, except (to limited degree) for polymorphic deserialization in which case TypeDeserializer helper object will coordinate use of type information (to once again find type-specific actual JsonDeserializer).

@Laures
Copy link
Author

Laures commented Feb 18, 2013

So you sort of need to build more distinct instances incrementally, such that deserializer will know what kind of type it will be dealing with as part of construction process.

i would do that, but the resolved property type is not available in a contextual way.

@cowtowncoder
Copy link
Member

I may have misunderstood your use case. So let me go back to basic question: how is your deserializer registered?

So far I assume you are registering them via Module interface, in which case type would definitely be available through process.
But there is then the other way to register (de)serializers: with direct annotation on property. In this case there is no callback to provide type as instance is simply constructed.
Is this the way deserializers in this case are constructed? If so, I see the problem.

@Laures
Copy link
Author

Laures commented Feb 19, 2013

thats exactly right. the deserializer is used for a property of the type List<T>. Jackson knows what T is during deserialization (either because it was annotated or because mapper.readValue(....) containt the type information. To properly deserialize the property i need that info.

currently i just use the ContextualDeserializer interface and look up the type from the BeanProperty. but the moment somebody does something fancy (interfaces, ....) i expect this to fail.

@cowtowncoder
Copy link
Member

Just one more clarification: is this used with something like:

@JsonDeserialize(contentUsing=MyValueSerializer.class)
public List<MyValue> values;

or by registering MyValueSerializer via Module API?

If latter, it still does not explain why you think type is not available. List (array, Map) deserializers do not handle value instances themselves, but delegate to value deserializers, and these are constructed and initialized like any other deserializers, including passing of type information.

Apologies for tons of questions -- I just want to make sure I fully understand problem being solved. At this point Jackson API is big (and thick) enough that I am trying to limit number of additions. I do agree in that type information would be good to pass, and if Contextual(De)Serializer was not an interface, I would add it there. But alas it is not... And additions then need to pass more checks due to need for bigger changes to support them.

@Laures
Copy link
Author

Laures commented Feb 19, 2013

its the annotation.

@cowtowncoder
Copy link
Member

Ok thanks. The request then makes sense.

Another way to handle this would be to bite the bullet and consider addition of (de)serializer factory/builder annotation. This would allow passing as much context as necessary, and would be easy to extend.

@RutledgePaulV
Copy link

What's the status on this? I too would find this helpful. I'm trying to define a custom annotation and deserializer combo to suit my needs and I'll need information about the target class of the deserialization in order to apply the settings from the annotation and return the correct class. I could almost get there by maintaining the generic chain, but I can't because there's no way to do this in java.

@JsonDeserialize(using=HybridDeserializer<SomeClass>.class)

So I would instead need to create subclasses for each different model, which is far from ideal.

@cowtowncoder
Copy link
Member

There is nothing planned or worked on for 2.5.0 to pass more information.

One thing that is added for 2.5.0 (due to be released soon) is actual value being deserializer (after instance has been initialized), via JsonParser.getCurrentValue().
But I don't know if that'd be of much used.

@cowtowncoder
Copy link
Member

Come to think of this, I don't think changing of ContextualDeserializer interface is feasible.
But adding minimal piece of state in DeserializationContext (for type of current contextualization call) could work -- just 2 calls. So maybe I'll sneak this in 2.5.0 after all, since it is a gap in functionality; use of BeanProperty is not reliably given structured types.

@cowtowncoder cowtowncoder added this to the 2.5.0 milestone Dec 27, 2014
@cowtowncoder
Copy link
Member

cowtowncoder commented Dec 27, 2014

This turned out relatively easy to implement, so now there is:

class DeserializationContext {
   public JavaType getContextualType() { ... }
}

which will give expected type during call to createContextual(), including case of deserializers that are directly added via annotation.

cowtowncoder added a commit that referenced this issue Dec 27, 2014
…ich can be called by contextual deserializers to know nominal type for them, without needing to decipher it from `BeanProperty`
@RutledgePaulV
Copy link

That's awesome. Thank you.

christophercurrie pushed a commit to christophercurrie/jackson-databind that referenced this issue Jul 19, 2015
@taypo
Copy link

taypo commented Jan 19, 2021

I don't understand how this will work? Do we have way to create deserializers without type information and then use getContextualType() inside the deserializer implementation?

@cowtowncoder
Copy link
Member

@taypo Not quite. You will need to implement ContextualDeserializer interface (just method createContextual()) and construct an instance that knows the target type -- either via BeanProperty passed (if available) or using method in DeserializationContext mentioned.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants