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

ObjectMapper serializes CharSequence subtypes as POJO instead of as String (JDK 15+) #3305

Closed
stevenupton opened this issue Oct 22, 2021 · 5 comments
Milestone

Comments

@stevenupton
Copy link

Describe the bug
I have a class that implements CharSequence and in Java 14 and below the class was serialised correctly.

In Java 15 the JSON generated is

{"empty": false}

I believe the change was introduced in the CharSequence API by the introduction of the method isEmpty().

It appears the POJOPropertiesCollector._addGetterMethod collects the method isEmpty();

The class is then identified as a Bean type object, and uses a BeanSerializer to serialize the class. In Java 14 ToStringSerializer would be used to serialize the class.

Version information
Jackson 2.11.3

To Reproduce

See simple class to reproduce:

`
public class TestMapper
{

private final static ObjectMapper mapper;     

static
{
    mapper = new ObjectMapper();
    mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
 
}


private static class TestObj implements CharSequence, Serializable
{

    private String str;
    
    private TestObj(String str)
    {
        this.str = str;
    }
    
    @Override
    public int length()
    {
        return str == null ? 0 : str.length();
    }

    @Override
    public char charAt(int index)
    {
        return str == null ? 0 : str.charAt(index);
    }

    @Override
    public CharSequence subSequence(int start, int end)
    {
        return str == null ? null : str.subSequence(start, end);
    }

    @Override
    public String toString()
    {
        return str;
    }
    
}

public static void main(String[] args)
{
    TestObj obj = new TestObj("Hello World");
    StringWriter writer = new StringWriter();
    try
    {
        mapper.writeValue(writer, obj);
        System.out.println(writer.toString());
    }
    catch(Exception e)
    {
        //this should not throw
    }
}

}

`

Expected behaviour
Expected "Hello World"

Actual
{"empty": false}

Running in Java 14 or below the expected result is returned

@stevenupton stevenupton added the to-evaluate Issue that has been received but not yet evaluated label Oct 22, 2021
@cowtowncoder
Copy link
Member

That sounds like a regression and has occasionally happened with at least one other JDK type (maybe Iterable or Iterator).

Need to figure out a way to avoid this -- but it can be rather nasty one to figure out, given that CharSequence can be a "tag" interface, not intended to be the main type. So POJO implementing CharSequence should typically NOT be serialized as CharSequence but as POJO. But determining this is not... trivial.
Conceptually I suspect that the heuristic would be to see that if the only detected property is empty, we'd consider fallback; and I think something like this was done for maybe Iterator handling. I wish remembered the issue.
We cannot really ignore isEmpty() in general as it is legit accessor; although in worst case we might want to prevent its detection at introspection for CharSequence. Except... that won't really help since it's an interface and something else will actually implement it.

@cowtowncoder cowtowncoder added 2.14 and removed to-evaluate Issue that has been received but not yet evaluated labels Oct 22, 2021
@stevenupton
Copy link
Author

stevenupton commented Oct 23, 2021

Hi I worked around the problem by forcing any class that implements CharSequence to use ToStringSerializer. I added the serializer to the ObjectMapper :

ObjectMapper mapper = new ObjectMapper();
SimpleModule m = new SimpleModule();
m.addSerializer(java.lang.CharSequence.class, new ToStringSerializer());
mapper.registerModule(m);

I'm not 100% convinced about this solution, as I may have a genuine Bean class that implements CharSequence. I did not want to single out the one class that was causing me problems either.

I was then thinking of implementing my own Serializer to check for Bean and instanceof CharSequence.. but the fix I added was OK for what I needed.

A fix or patch would be appreciated.

@cowtowncoder
Copy link
Member

Right, that does work due to how SimpleModule does matching -- it will force, in this case, any type that implements CharSequence to be serializer as String. That's fine for users to do, but probably unsafe as general default behavior for Jackson core itself (and would be backwards-incompatible change).

I agree that this needs to be addressed; above is just thinking out aloud about best ways to do it.

@cowtowncoder cowtowncoder changed the title ObjectMapper serializes JSON as using a BeanSerializer instead of ToStringSerializer in Java 15 ObjectMapper serializes CharSequence subtypes as POJO instead of using ToStringSerializer in Java 15 Nov 23, 2021
@seregamorph
Copy link

Duplicated issue: #3331 (closed) with similar example

@cowtowncoder
Copy link
Member

Quick note: working on this, but having big difficulties in getting Eclipse setup to work with specific JDK/JRE I need.
Test can be run from command line but want to figure out IDE as well. So this is at the top of my stack right now.

@cowtowncoder cowtowncoder added this to the 2.13.1 milestone Dec 11, 2021
@cowtowncoder cowtowncoder changed the title ObjectMapper serializes CharSequence subtypes as POJO instead of using ToStringSerializer in Java 15 ObjectMapper serializes CharSequence subtypes as POJO instead of as String (JDK 15+) Dec 11, 2021
cowtowncoder added a commit that referenced this issue Dec 11, 2021
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

3 participants