jackson-databind icon indicating copy to clipboard operation
jackson-databind copied to clipboard

Exception while deserializing with @JsonRootName

Open pritibiyani opened this issue 6 years ago • 15 comments

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
}

pritibiyani avatar Feb 11 '19 12:02 pritibiyani

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.

cowtowncoder avatar Feb 11 '19 18:02 cowtowncoder

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?

pritibiyani avatar Feb 11 '19 18:02 pritibiyani

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 avatar Feb 19 '19 14:02 efenderbosch

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

efenderbosch avatar Feb 19 '19 14:02 efenderbosch

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.

pritibiyani avatar Feb 20 '19 03:02 pritibiyani

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.

cowtowncoder avatar Feb 22 '19 01:02 cowtowncoder

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.

efenderbosch avatar Feb 22 '19 15:02 efenderbosch

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.

cowtowncoder avatar Feb 23 '19 01:02 cowtowncoder

@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!

pritibiyani avatar Feb 23 '19 19:02 pritibiyani

@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.

cowtowncoder avatar Feb 27 '19 02:02 cowtowncoder

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?

pritibiyani avatar Mar 12 '19 16:03 pritibiyani

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 avatar May 21 '19 12:05 efenderbosch

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

efenderbosch avatar May 21 '19 15:05 efenderbosch

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 avatar May 21 '19 18:05 cowtowncoder

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
}

onacit avatar Aug 01 '24 06:08 onacit