question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Feature request: registering subtypes without annotating the parent class

See original GitHub issue

When using @JsonTypeInfo with use = Id.NAME, it is currently required to provide the name to class map with the @JsonSubTypes annotation on the parent class. This is problematic if a third-party wants to extend my code: they will have to modify my code to register their new subclass.

It would be fantastic if the mapping could be provided via annotations on the subclasses themselves. Something like this:

@JsonTypeInfo(use = Id.NAME,
              include = JsonTypeInfo.As.PROPERTY,
              property = "type")
public interface Parent {
     // ...
}

@JsonTypeName(name = "first-child")
public class FirstChild extends Parent {
    // ... 
}

@JsonTypeName(name = "second-child")
public class SecondChild extends Parent {
   // ...
}

This would require retrieving at runtime the subclasses and building the map based on their annotations. This can already be achieved by implementing a custom TypeIdResolver but the use case seems to be fairly common so I think it would make sense to add this to Jackson directly.

Related SO question: https://stackoverflow.com/questions/34079050/add-subtype-information-at-runtime-using-jackson-for-polymorphism

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

2reactions
cowtowncodercommented, Mar 8, 2019

A few workable ideas: registerSubtypes() (esp. via Module) is the way to go. I am not sure I would want to have functionality for this directly in databind, but if someone wants to work on general-purpose extension module, that could be added under FasterXML if it proves useful.

My experiences with SPI for Java 8 and earlier make me pretty skeptic of using it for anything (and possible even more so for registering services actually… performance is horrendous, no way to deal with duplicates, mere existence of something in classpath changing things in weird and non-reproducible ways). But then again, it is a somewhat documented mechanism and Jackson does expose metadata already and even allows auto-registering of Modules (which I personally never use and do not recommend but that’s matter of taste 😃 ). And Java 9+ module path probably makes access less inefficient as well, so as mechanism maybe it is becoming more usable.

So: if anyone wants to create prototype module, great; that would be reasonable way to add such functionality for users I think.

1reaction
toadzkycommented, Mar 6, 2019

There’s a pattern used by the Dropwizard framework that works pretty well. Put the subclasses in the META-INF/services directory under the interface and just load the file, iterating through the lines of the file in a Jackson module calling registerSubtypes. Then you can just annotate each class with @JsonTypeName and it just works.

Example: META-INF/services/package.Parent

package.FirstChild
package.SecondChild

In your jackson module:

class MyJacksonModule extends SimpleModule {
    public MyJacksonModule() {
        final List<Class<? extends T>> serviceClasses = new ArrayList<>();
        try {
            // use classloader that loaded this class to find the service descriptors on the classpath
            // better than ClassLoader.getSystemResources() which may not be the same classloader if ths app
            // is running in a container (e.g. via maven exec:java)
            final Enumeration<URL> resources = getClassLoader().getResources("META-INF/services/" + serviceType.getName());
            while (resources.hasMoreElements()) {
                final URL url = resources.nextElement();
                CharStreams.readLines(new InputStreamReader(url.openStream(), StandardCharsets.UTF_8)).forEach(line -> {
                    @SuppressWarnings("unchecked")
                    final Class<? extends T> loadedClass = (Class<? extends T>) loadClass(line);
                    if (loadedClass != null) {
                        serviceClasses.add(loadedClass);
                    }
                });
            }
        } catch (IOException e) {
            log.warn("Unable to load META-INF/services/{}", serviceType.getName(), e);
        }
        registerSubtypes(serviceClasses);
    }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

java - Add SubType information at runtime using Jackson for ...
I ended up using Reflections library to find all subtype of Animal class, and register JsonSubTypes with mapper.registerSubtypes(Class<?> ...
Read more >
Handle All Subtypes with Custom Deserializer Without Explicit ...
Hello, I have a supertype (interface to be specific, but hopefully that should not matter) for which all extended subtypes should use a...
Read more >
Inheritance in Jackson | Baeldung
This tutorial will demonstrate how to handle inclusion of subtype metadata and ignoring properties inherited from superclasses with Jackson.
Read more >
Management & Monitoring - Micronaut Documentation
Many features of the Micronaut framework rely on the convention of getters and setters. ... It can also be used with classes annotated...
Read more >
Java Generics Example Tutorial - Generic Method, Class ...
Java Genrics is one of the most important features introduced in Java 5. If you have been working on ... Java Generic Classes...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found