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

Exception while deserializing with @JsonRootName #2253

Open
pritibiyani opened this issue Feb 11, 2019 · 15 comments
Open

Exception while deserializing with @JsonRootName #2253

pritibiyani opened this issue Feb 11, 2019 · 15 comments

Comments

@pritibiyani
Copy link

Desirialization does not work when there are multiple nodes available at same level as value provided in the JsonRootName

Note:
Wrapper is configured with following:

objectMapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true);

Class:

@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)

@JsonRootName(value = "response")
public class UserProfile {
  String name;
  String link;
}

Input JSON (which works):

{
  "response": {
    "name": "User one:",
    "link": "Some Link"
  }
}

Input JSON (which does not work)

{
  "response": {
    "name": "User one:",
    "link": "Some Link"
  }, 
  "apiVersion" : 1.0
}

@cowtowncoder
Copy link
Member

Correct: it is expected there is exactly one root-level property to match. Feature is most commonly used with XML content (and basically was added for compatibility with XML-to-json processing libraries like Jettison) where there can only be one root-level element.

So this works as expected. I am open to suggestions for better documentation if that would help.

@pritibiyani
Copy link
Author

pritibiyani commented Feb 11, 2019

So what would be suggested a way to handle the above scenario?

Write a class which would wrap the outer node?

Also, yes we can give some examples and add a note stating the same in the documentation. Basically emphasizing on when it would work and when it wouldn't. What do you think?

@efenderbosch
Copy link

Also looking for help with this. Any suggestions? The standard API call has the correct root element, but apparently some of our clients are also sending other metadata top-level fields. We can safely ignore them, but the root unwrapping is throwing MismatchedInputException.

@efenderbosch
Copy link

It feels like FAIL_ON_TRAILING_TOKENS should ignore this? The default is false, but it still throws the exception.

@pritibiyani
Copy link
Author

I guess it would not work is what @cowtowncoder told.

@cowtowncoder can you give an example or redirect us to one if it exists already? This would help us a lot!

Thanks a ton in advance.

@cowtowncoder
Copy link
Member

I don't think FAIL_ON_TRAILING_TOKENS is not applicable here (unless exception states it is) -- rather it's UNWRAP_ROOT_VALUE that requires that there's just one root-level entry.
Including exception might help.

I think I would suggest this is invalid usage, partly since JSON logical model does not guarantee ordering, and unwrap functionality does not deal with that.

But I think I would be interested in finding a solution for specific use case. It is probably possible to model in a way that works. But a non-trivial problem is that Lombok seems to be used which limits workarounds a bit.

@efenderbosch
Copy link

What about a non-Lombok use case?

Could there just be a new DeserializationFeature? Like IGNORE_EXTRA_ROOT_ELEMENTS or something? The unwrap functionality should already know the desired root element name from the annotation.

@cowtowncoder
Copy link
Member

cowtowncoder commented Feb 23, 2019

I am bit wary of either changing definitions of features involved, or adding a single-use feature.
But I can see the point of request itseld.

I will have to think more about handling here: a significant part of hesitation is the fact that I don't know how involved it would be to allow original code to work as shown, for both XML and JSON cases.

I'll tag this as 2.10 since behavioral change is involved.

@pritibiyani
Copy link
Author

@efenderbosch The feature request makes sense as this would have lots of usecases in JSON.

@cowtowncoder The same could be applicable for XML as well, wouldn't it?

  • Both can have multiple root and just want to unwrap one of those.
    looks a valid use case for both formats!

@cowtowncoder
Copy link
Member

@pritibiyani No, not really, XML and JSON are NOT identical structurally: there is impedance regarding names. So, following might represent identical logical content:

{ "x" : 1, "y" : 2 }
<point>
  <x>1</x>
  </y>2<y>
</point>

and so any "root name" in case of XML must be for point; and since XML does not allow more than one root element, one can never match anything else.

The original reason to support @JsonRootName (and unwrapping) for json was, however, that some legacy json libraries would map such logical content as

{ "point" : {
   "x" : 1,
   "y" : 2
}}

which is redundant and clumsy, but is easier to convert to/from xml.

@pritibiyani
Copy link
Author

Perhaps there is chance to improve the documentation.

Also, is there any way we could only provide this for JSON? Do we have any other use cases, where we need to implement certain functionality only for XML and/or JSON?

@efenderbosch
Copy link

Maybe just another method could be added to DeserializationProblemHandler so we could catch/override this issue? I was hoping handleUnexpectedToken would work, but it doesn't.

@efenderbosch
Copy link

In the meantime, @pritibiyani you can try something like this: https://gist.github.com/efenderbosch/36354b0abc1083d63f70c44687e1cafb

@cowtowncoder
Copy link
Member

Although addition of a method in DeserializationProblemHandler is a viable option in general, handling happens at a point where it becomes bit unwieldy, and it's not clear if any other option except for "skip the rest" would make sense. I'll have to think about this more.

@pritibiyani Question on format-specific annotations is interesting one -- I have occasionally thought about this as it does seem like there are cases where optional inclusion would make sense (only include on X, or exclude from Y). This could possibly be doable with some sort of wrapper/container annotation, although would need to enumerate what annotations would be allowed within.
Regardless I think change is big enough that it would only make sense for 3.0 as annotation introspection functionality would need a significant refactoring.

Alternative possibility, for same goal, could be to allow ObjectMapper to "disable" use of specific set of annotations: they could be pruned at collection time. This would probably be smaller change and perhaps could go in 2.x.

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

onacit commented Aug 1, 2024

I'm not sure the problem's been solved. Let me share what I did.

<point>
  <x>1</x>
  </y>2<y>
</point>
{
  "point" : {
    "x" : 1,
    "y" : 2
  }
}

I'm not allowed @JsonRootName and that's why I defined the following class.

@XmlRootElement
class AbstractType<SELF extends AbstractType<SELF>> {

    @JsonIgnore
    @XmlTransient
    public SELF get() { // should be used for getting the result.
        if (wrapped != null) {
            return wrapped;
        }
        return this;
    }

    @XmlTransient
    @Setter
    @Getter
    private SELF wrapped;
}

class Point extends AbstractType<Point> {

    @JsonProperty("point")
    public Point getWrapped() {
        return super.getWrapped();
    }
}

Above class deserialise both following JSON.

{
  "point" : {
    "x" : 1,
    "y" : 2
  }
}
{
  "x" : 1,
  "y" : 2
}

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

4 participants