Jackson-databind: @JsonUnwrapped not supported for Map-valued properties

Created on 20 Feb 2013  Â·  24Comments  Â·  Source: FasterXML/jackson-databind

According to the documentation,

Annotation used to indicate that a property should be serialized "unwrapped"; that is, if it would be serialized as JSON Object, its properties are instead included as properties of its containing Object.

Unfortunately, this seems to work only with bean types, and does not work with Map<String, Object>. Given that bean types are generally completely interchangeable with Maps, it would be very nice if this worked correctly.

most-wanted

Most helpful comment

Unfortunately the @JsonAnyGetter does not work (easily) with Kotlin:

class Links(
        @JsonAnyGetter
        val links: Map<String, Link>
) : Serializable

as the annotation needs to put on a method.

!! UPDATE !!

It does work (easily):

class Links(
        @get:JsonAnyGetter
        val links: Map<String, Link>
) : Serializable

All 24 comments

Hmmh. Unfortunately Maps and POJOs are internally handled rather differently. But I am all for unification if it is technically possible, so limitation is not philosophical, just practical. I suspect that serialization part should be easy enough to make work; deserialization might get trickier?

It would be very cool if this worked :smile:

:-)

One quick question: I assume most users would expect it to work both ways. But I suspect serialization is much easier to make work. Would you find any value in serialization-only impl? (especially initial one)

One more thing: as per this:

http://www.cowtowncoder.com/blog/archives/2011/07/entry_458.html

one can use @JsonAnyGetter/setter to do something possibly similar. One missing pieces is that currently one must have getter (can't use it on Map filed), but that should be easy enough to address.

Yes, a serialization-only implementation would solve my immediate use case, although I can see the deserialization as being extremely useful as well.

It does seem that this could potentially be done with serialization-only JsonUnwrapped usage and then JsonAnySetter to handle the deserialization case, although that feels more than a little bit janky to me.

Yes my concern is really with unexpected one-way street: unwrapping on way out, but not "wrapping it back" as Map on way back in.

Ok, I think supporting this would be very useful for CSV, f.ex see https://github.com/FasterXML/jackson-dataformat-csv/issues/25
so maybe I should go back, try to tackle this.

Although, with CSV there are other open questions due to name/index mapping. But still, solving this on databind side could help.

+1

+1

A related note for anyone who happens upon this issue: one alternative is use of @JsonAnyGetter, which does allow functionality for a single Map.

Great! @JsonAnyGetter is exactly what I was looking for. Thanks!

I find this behavior surprising at a conceptual level, since it seems to be contrary to how Jackson behaves in general.

Would I be correct in assuming that this is one of the features that will likely not be in the next Jackson 2.9 release, per the Changes likely to be postponed section?

@matthew-pwnieexpress You are correct in that no work is planned for this feature.
I can see how this is unexpected from users perspective: difference is due to technical evolution of backend implementation where Maps and POJOs have very different handling. But conceptually this should not surface quite this strongly, or, if it does, would need to be explained and documented much better.

@cowtowncoder Given that @JsonAnyGetter and @JsonAnySetter exist, how correct/incorrect would an implementation be to have @JsonUnwrapped imply the other two annotations when used with MapSerializer?

@henryptung Interesting thought... hmmh. There is also then the possible question of what if more than one such "any property" is declared.
I guess this is not problematic with POJOs as their properties are somewhat defined and multiples unwrapped POJOs may co-exist; whereas any-x is fallback.

Unfortunately the @JsonAnyGetter does not work (easily) with Kotlin:

class Links(
        @JsonAnyGetter
        val links: Map<String, Link>
) : Serializable

as the annotation needs to put on a method.

!! UPDATE !!

It does work (easily):

class Links(
        @get:JsonAnyGetter
        val links: Map<String, Link>
) : Serializable

True; as of now (2.9), @JsonAnyGetter must be on method. In theory it could be extended to fields, to allow construction of Map instance. One challenge would be the fact that semantics would be slightly different -- in one case method gets fed key/value pair: that could lead to confusion (unless logic further added to perhaps also allow single Map-argument... but that can lead to another can of worms).

We face the challenge that we provide a wrapper type for some content object that could either be a custom object or a Map:

class SomeWrapper<T> {

  T content;

  @JsonUnwrapped
  public getContent() {
    return content;
  }
}

Is there something we could do using a custom serializer to not have to add the extra method or extra class?

@odrotbohm I can't think of anything: @JsonUnwrapped is quite tied to way BeanSerializer and -Deserializer works and although one can override handlers I am not sure custom (de)serializer route would lead to anything but fragile solution.

I have a POC with the following steps:

  • register a custom StdSerializer<SomeWrapper>
  • in that, inspect the content. If it's a Map take the content and wrap it into a type using @JsonAnyGetter, default serialize that. If no, wrap into an almost copy of SomeWrapper using a plain @JsonUnwrapped

That way, SomeWrapper stays the only user facing API but does the right thingâ„¢ during serialization.

Incidentally, the Stack Overflow question for this issue has 46 upvotes, and its top answer has 101 upvotes, so this is definitely an issue users have been encountering.

Ok, marking this as "most-wanted" as there are a few thumbs-ups here too.

About the only question I have is whether there are some specific differences between just using @JsonAnyGetter/@JsonAnySetter combo, and potential @JsonUnwrapped.
Using one less annotation seems like nice-but-not-essential; consistency a minor plus too.
Or put another way: if @JsonUnwrapped was essentially implemented as sort of alias for "any-getter", would that work?
(another potential concern: can only have one "any getter/setter" per class -- but multiple @JsonUnwrappeds)

It's also very surprising it doesn't work for ObjectNodes, e.g., the trivial case JsonNodeFactory.instance.objectNode().put("sample", 1). I would guess that's because under the covers, ObjectNode is a map, but to someone writing that code, it's just a json object. And the @JsonAnyGetter trick doesn't work for this.

@Vroo Conceptually, JsonNode types are not POJOs, so most annotations do not have any effect by design. It would be good to document this better as I can see why it might be surprising... there isn't much documentation on concept, intended differences. In fact, POJOs, "untyped" (Object / List / Map), Trees (JsonNode) are all somewhat different models within Jackson, and while they interoperate fine, they are not treated the same way.

@Vroo that said, I can see why specific cases of @JsonAnyGetter/@JsonAnySetter would make sense for JsonNode/ObjectNode values -- if so, feel free to file an issue to add support for that usage.

@JsonUnwrapped is a bit trickier just because the whole machinery for it to work is... rather complicated and fragile: you could file an issue for that, too (since adding support for Maps would be technically different from JsonNode; there isn't much synergy in getting both implemented)

Was this page helpful?
0 / 5 - 0 ratings