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

Nested @JsonUnwrapped property names not correctly handled (caching?) #2461

Open
plovell opened this issue Sep 17, 2019 · 12 comments
Open

Nested @JsonUnwrapped property names not correctly handled (caching?) #2461

plovell opened this issue Sep 17, 2019 · 12 comments
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue

Comments

@plovell
Copy link

plovell commented Sep 17, 2019

We are using @JsonUnwrapped to serialise objects with three levels of nesting. If we serialise the top level object first, then we get the JSON we expect, but if we serialise the second level object first and then try to serialise the top level object, it is as if the @JsonUnwrapped annotation is ignored for the top level object. We are using Jackson databind version 2.9.9.3.

The following test case demonstrates the problem:

@Test
public void jsonMappingOrderTest() throws JsonProcessingException {
    ObjectMapper mapperOrder1 = new ObjectMapper();
    ObjectMapper mapperOrder2 = new ObjectMapper();

    BaseContainer inner = new BaseContainer(new Base("12345"));
    BaseContainerContainer outer = new BaseContainerContainer(inner);

    assertEquals("{\"container.base.id\":\"12345\"}", mapperOrder1.writeValueAsString(outer));
    assertEquals("{\"base.id\":\"12345\"}", mapperOrder1.writeValueAsString(inner));
    assertEquals("{\"container.base.id\":\"12345\"}", mapperOrder1.writeValueAsString(outer));

    assertEquals("{\"base.id\":\"12345\"}", mapperOrder2.writeValueAsString(inner));
    //  Will fail here
    assertEquals("{\"container.base.id\":\"12345\"}", mapperOrder2.writeValueAsString(outer));
}

static class Base {
    private String id;

    Base(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }
}

static class BaseContainer {
    private Base base;

    BaseContainer(Base base) {
        this.base = base;
    }

    @JsonUnwrapped(prefix = "base.")
    public Base getBase() {
        return base;
    }
}

static class BaseContainerContainer {
    private BaseContainer container;

    BaseContainerContainer(BaseContainer container) {
        this.container = container;
    }

    @JsonUnwrapped(prefix = "container.")
    public BaseContainer getContainer() {
        return container;
    }
}
@cowtowncoder
Copy link
Member

That sounds wrong. Thank you for reporting the problem.

cowtowncoder added a commit that referenced this issue Sep 17, 2019
@cowtowncoder
Copy link
Member

Yes, I can reproduce that. Gnarly...

@cowtowncoder cowtowncoder changed the title @JsonUnwrapped property names for nested objects can be incorrectly cached in an ObjectMapper Nested @JsonUnwrapped property names not correctly handled (caching?) Sep 25, 2019
@cowtowncoder
Copy link
Member

Trying to figure this out: not sure if this is specifically related to caching, but is definitely due to something in logic that handles contextualization related to fetching serializer.

@cowtowncoder
Copy link
Member

Ok. I give up, for now. I suspect this is due to multiple levels of nesting, and contextualization being applied by one level, but not across multiple levels.

So at this point I would recommend not attempting unwrapping to be used for nesting more than one level.

@plovell
Copy link
Author

plovell commented Sep 25, 2019

Thanks for looking.

We do have a workaround that seems to function correctly.

We use the object mapper effectively as a singleton and we have found that if we ensure that the equivalent object to BaseContainerContainer is always serialised first then we no longer get the error when serialising either of the two objects. So we initialise our object mappers on application startup using an example object instance.

@cowtowncoder
Copy link
Member

@plovell Glad to hear you have a work-around. I hope I can eventually figure this out -- I was really hoping to find a solution in time for 2.10.0, but that is not happening.

But there is now a test, and at least it is not (I think!) something specific about mapper-wide caching: it may well be due to per-property serializer caching but that would be more local to solve.

One other thing I was thinking but forgot to write down is that specifying

    @JsonSerialize(typing = Typing.STATIC)

for property might help, as it would allow eager fetching of serializer(s). But I did not test this so have no idea if it can make things work correctly.

@cowtowncoder cowtowncoder removed the 2.10 label Apr 7, 2020
@caiquebispoferreira
Copy link

Is there any solution for that?
I have a similar issue but it just happens on my application when it is running on Ubuntu.
Both applications (Windows and Linux) are using the JDK 15.0.2.
What's the simplest solution for that?

@cowtowncoder
Copy link
Member

There is no solution to this problem at this point. You may have to refactor handling to eliminate a level, or flatten object structure (you can use "multi-level" naming like @JsonUnwrapped(prefix="base.container.")).

@caiquebispoferreira
Copy link

caiquebispoferreira commented Mar 16, 2021

Thank you for your response.
I am trying to find other ways that I could do a similar thing minimizing efforts to serialize and deserialize.
Is there any way to set up the object mapper to unwrap all custom class objects by default using the property name as the prefix?

from:

{
     myObject : {
        a : 1,
        b: {
          c: "hi"
        }
      },
     d : ""
}

to:

{
      myObject_a : 1,
      myObject_b_c : "hi",
      d :  ""
}

What would you recommend?

@cowtowncoder
Copy link
Member

@caiquebispoferreira No, not really. Perhaps you could extend JacksonAnnotationIntrospector and override method that is called to detect @JsonUnwrapped, to indicate desire to "find" unwrapping information based on whatever logic you want (method findUnwrappingNameTransformer(...)).
It would only need to handle a single layer as introspection happens recursively as necessary.

@cowtowncoder cowtowncoder added the has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue label Oct 2, 2023
@cowtowncoder
Copy link
Member

@JooHyukKim Here's an old and tricky problem -- there's a (failing) unit test for reproducing. Just in case you wanted to find yet more things to work on :)

(was reminded of this as the issue was reported on Kotlin module)

@JooHyukKim
Copy link
Member

Just sharing things in case it might help anybody. So ordering does matter. My observation is that caching issue happens at UnwrappingBeanPropertyWriter.java#L224. Disabling it makes UnwrappedCaching2461Test pass.

While my analysis on @JsonUnwrapped continues, potential approaches might involve:

  • Managing "multi-level" unwrapping or,
  • Employing back-tracking during serialization.

These thoughts are just draft, open for guides or collaboration 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
has-failing-test Indicates that there exists a test case (under `failing/`) to reproduce the issue
Projects
None yet
Development

No branches or pull requests

4 participants