@JsonCreator inherits from superclass?

1,494 views
Skip to first unread message

Rich MacDonald

unread,
Sep 4, 2013, 11:01:06 AM9/4/13
to jackso...@googlegroups.com
It looks to me like the @JsonCreator must be declared on every concrete class where it used. One cannot declare a single static @JsonCreator annotated method in the superclass, right? When I do that, the objectMapper doesn't find it when instantiating a subclass.

My particular problem is that I need to override the constructors for a class hierarchy which has a few thousand subclasses. (So even the mixin approach is inconvenient.) If I could define  @JsonCreator in a static superclass method, I could inspect the @class property and handle the newInstance() myself. I am writing the code to create the instance from the Map<String,Object argument myself, but that argument can be inside Arrays and Collection, so I don't really want to implement the TreeNode style.

Any ideas appreciated. TIA, Rich MacDonald

Rich MacDonald

unread,
Sep 4, 2013, 11:40:46 AM9/4/13
to jackso...@googlegroups.com
The ValueInstantiator is probably what I need. Not to look a gift horse in the mouth, but that documentation section appears a little thin :) Trying to work my way through it...

Rich MacDonald

unread,
Sep 4, 2013, 2:18:33 PM9/4/13
to jackso...@googlegroups.com
Oh dear. ValueInstantiator suffers from the same problem that it cannot be declared in a superclass and used for subclasses.

At least now I can register all my subclasses dynamically at startup with just a few lines of code (I have a runtime list of all classes.)

I have never tried to write a Deserializer. Will that work or does it have the same issue as the @JsonCreator and ValueInstantiator?

Tatu Saloranta

unread,
Sep 4, 2013, 10:59:37 PM9/4/13
to jackso...@googlegroups.com
First of all yes, constructor annotations are not inherited, since it seemed like a risky idea at the time (would correct constructors match? What if sub-class was missing constructor with same signature? Would it be confusing).
Having said that, I would entertain the idea of supporting inheritance here if someone filed an RFE at jackson-databind project -- I don't remember if handling this would be easy or not in code that flattens annotations through object hierarchy.

But you could probably handle this by sub-classing JacksonAnnotationIntrospector, and overriding method that is used to detect @JsonCreator annotation. It's some work, but completely doable: you just check parent class(es) recursively, until annotation is found.

-+ Tatu +-



--
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 jackson-user...@googlegroups.com.
To post to this group, send email to jackso...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Rich MacDonald

unread,
Sep 6, 2013, 1:57:55 PM9/6/13
to jackso...@googlegroups.com
Good to know I am not going crazy.

While I think supporting constructor annotation inheritance would be useful, I did some extra work and realized that StdDelegatingDeserializer is a better approach, as I am converting my Domain objects to and from Map<String,Object> anyway. Working through that, I ran into the fact that SimpleDeserializers looks up matches via the _classMappings attribute. And it does so by exact class matching, rather than by class inheritance. So I'd still have to register my single instance of StdDelegatingDeserializer individually for a few thousand classes.

The better implementation for me would be to modify the _classMappings lookup to match on the entire class hierarchy for a ClassKey.I.e., try to match on class "A", then match on class A's superclass "B", the superclass "C", etc.

Cleanest way to make that change would be a subclass of HashMap<ClassKey,JsonDeserializer<?>> that overrides the get() method. Unfortunately the ClassKey._class attribute is private, so walking up the class hierarchy is an ugly "new ClassKey(Class.forName(classKey.toString()).superClass())" codewart. (And obviously, overriding a HashMap class isn't exactly a "best practice". :)

I'd suggest that the following line in SimpleDeserializers be refactored out into a separate method, which can then be overridden cleanly::

return (_classMappings == null) ? null : _classMappings.get(new ClassKey(type.getRawClass()));

In the meantime, I just subclassed SimpleDeserializers  and overrode the 8 find... methods to walk up the class hierarchy.

This ...... almost ..... worked ....

I had the following:

-------------
SimpleModule module = new SimpleModule("MyData", Version.unknownVersion());           
module.setDeserializers(new HierarchicalDeserializers()); //HierarchicalDeserializers is my subclass of SimplerDeserializers
module.addDeserializer(MyData.class, new StdDelegatingDeserializer<MyData>(new StdConverter<Map<?,?>, MyData>() {
@Override public MyData convert(Map<?,?> map) {
//handle instance construction and population;
}
}));

The problem is that the map argument to the StdConverter.convert() does NOT contain any information about the particular subclass. IOW, it has all the attributes, but not the "@class" parameter with the classname. Not that I expected it would, but some type information passed along as a method parameter would have been perfect. Oh well...

--------
I spent the day struggling through the various factories and things. Finally decided to do a lazy instantiation of an StdDelegatingDeserializer inner class for each requested subclass of MyData. I pass the class in through the constructor, so the instance always know what subclass it is dealing with.

Tatu Saloranta

unread,
Sep 6, 2013, 4:04:19 PM9/6/13
to jackso...@googlegroups.com
Instead of using SimpleModule, and SimpleDeserializers, you are probably better off using a custom `Deserializers` implementation, so instead of having to specify a key match, you get type and can use your own logic. SimpleXxx are there to handle simple cases; and in general it is not safe to assume that sub-classes can be handled by super-class deserializer.

Yet another alternative would be to use BeanDeserializerModifier (and specifically its `modifyDeserializer()` method), which also let's you hi-jack flow of getting a deserializer; it gives you default JsonDeserializer that would be used, and you can replace it or wrap another deserializer around it (use default in some cases, something else in others).

Hope this helps,

-+ Tatu +-



--
Reply all
Reply to author
Forward
0 new messages