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.

activateDefaultTyping does not work with inner class in a collection like ArrayList

See original GitHub issue

Found in Jackson 2.11.2. Suppose we put a some objects into a list then serialize it. If the object is from a normal class, everything works. But if it’s an inner class, the type of each element is erased as “java.util.Object”. If you have bean wrapper to the ArrayList, or if you define the MyObj class as a standard alone class (not an inner class), it will work.

Below is the test class.

    @Test
    public void test() throws JsonProcessingException {
        ObjectMapper mapper = new ObjectMapper();
        mapper.activateDefaultTyping(new DefaultBaseTypeLimitingValidator(), ObjectMapper.DefaultTyping.EVERYTHING, JsonTypeInfo.As.PROPERTY);

        List<MyObj> list = new ArrayList<>();

        MyObj o1 = new MyObj();
        o1.v1 = 1;
        list.add(o1);

        String json = mapper.writeValueAsString(list);
        System.out.println(json);
        // it prints ["java.util.ArrayList",[{"@class":"java.lang.Object","v1":1}]]
    }

    class MyObj {
        int v1;

        public int getV1() {
            return v1;
        }

        public void setV1(int v1) {
            this.v1 = v1;
        }
    }

If you look at the ClassNameIdResolver, when we see an inner class, I don’t quite understand the logic here, anyway the str loses valuable information about the actual class name, becomes “java.util.Object”. Why do we use the base type? why not using the actual class name here?

        } else if (str.indexOf('$') >= 0) {
            /* Other special handling may be needed for inner classes,
             * The best way to handle would be to find 'hidden' constructor; pass parent
             * value etc (which is actually done for non-anonymous static classes!),
             * but that is just not possible due to various things. So, we will instead
             * try to generalize type into something we will be more likely to be able
             * construct.
             */
            Class<?> outer = ClassUtil.getOuterClass(cls);
            if (outer != null) {
                // one more check: let's actually not worry if the declared static type is
                // non-static as well; if so, deserializer does have a chance at figuring it all out.
                Class<?> staticType = _baseType.getRawClass();
                if (ClassUtil.getOuterClass(staticType) == null) {
                    // Is this always correct? Seems like it should be...
                    cls = _baseType.getRawClass();
                    str = cls.getName();
                }
            }
        }
        return str

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
cowtowncodercommented, Sep 5, 2020

There is one test class, TestAbstractTypeNames, which has this as use case:

    static class BaseValue {
        public int value = 42;

        public int getValue() { return value; }
    }

    final static class BeanWithAnon {
        public BaseValue bean = new BaseValue() {
            @Override
            public String toString() { return "sub!"; }
        };
    }

I am not 100% sure if this is actually in use: from comment in test it looks like original issue was against Jackson 1.x so probably 8+ years ago…

However, at this point I don’t feel like it should be blocked for 2.x. But I will remove handling from 3.0.

0reactions
cowtowncodercommented, Sep 1, 2020

I think earlier code may have thrown an exception (as I agree it is good to indicate invalid use cases). But unfortunately I am not sure it is possible to detect definitely invalid usage. I think the expected working case is something like:

List<String> stuff = new ArrayList<String>() {  add("first"); add("second"); };
String json = mapper.writeValueAsString(stuff);

where anonymous non-static inner class is to be written and logic can figure out type to use can be ArrayList.

The other possibility is that sometimes users only serialize things using Jackson and use something else for reading content.

It may be easy enough to test to see what happens if code is changed to throw an exception in assumed bad case and running unit tests. I think a test would fail, showing expected correct usage.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Can't add a value to ArrayList because it's in an Inner Class?
I am writing a file that can parse rdf and owl files. I am using SAX and Java. My problem is on the...
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 >
Nested Classes - Learning the Java Language
Non-static nested classes (inner classes) have access to other members of the enclosing class, even if they are declared private. Static nested classes...
Read more >
Inner/Nested Classes Collection Implementation
So A implements the code, gives something to B, where B can invoke the code later. • Inner classes are widely used to...
Read more >
An inner class - Cornell CS
ArrayList s contains one object of static nested class Entry for each integer in the set; that object contains the integer and the...
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