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

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.

About this issue

  • Original URL
  • State: closed
  • Created 11 years ago
  • Reactions: 25
  • Comments: 34 (16 by maintainers)

Commits related to this issue

Most upvoted comments

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

About the only question I have is whether there are some specific differences between just using @JsonAnyGetter/@JsonAnySetter combo, and potential @JsonUnwrapped. … Or put another way: if @JsonUnwrapped was essentially implemented as sort of alias for “any-getter”, would that work?

I have the same problem as @odrotbohm: @JsonUnwrapped sits on top of a generic. To get it to work, I’ve needed to create subclass that overrides the getter so I can add @JsonAnyGetter. The any-getter doesn’t work because it’s of type T, not a Map and it throws an error during runtime otherwise.

This creates other problems because I’ve subclassed it. Being able to use @JsonUnwrapped would be much easier, cleaner and safer.

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

Hi, I’d like to add here my use-case:

ugly Data pretty Data OtherFieldsJacksonTest The test passes with “ugly Data”, but obviously not with “pretty Data”.

I’d like to collect the extra fields into a separate Map, then do some work with the data object, then serialize it back to json string. I don’t need them for anything, but I want to preserve them if I later decide I do need them.

When I define the record like this, the serialization works as expected and deserialization doesn’t throw errors, but it puts null into the otherFields field.

pretty Data I’ve done some more googling and this is basically the same #3439 (comment), sorry for a duplicate post

Hi! @fprochazka, you can try to solve this for your “pretty Data” with a static method annotated with @JsonCreator:

public record Data(
    @JsonProperty("Status") String status,
    @JsonAnyGetter Map<String, Object> otherFields
) {
        @JsonCreator
        public static Data create(Map<String, Object> attributes) {
            Object status = attributes.remove("Status");
            assert status instanceof String;
            return new Data((String) status, attributes);
        }
}

@cowtowncoder sorry, I thought it is relevant to this issue, since I didn’t find the sample anywhere previously

I had a go about this and could get Unwrapping aspect working. The JsonSerializer<T> has a unwrappingSerializer method that is called within UnwrappingBeanPropertyWriter that sort of lifts the serializer into a unwrapping serializer. When we try to serialize a Map instance a MapSerializer is responsible for the serialization step. Overriding unwrappingSerializer for MapSerializer to create a UnwrappingMapSerializer should give us what we want.

@Override
public JsonSerializer<Map<?, ?>> unwrappingSerializer(NameTransformer transformer) {
    return new UnwrappingMapSerializer(this, transformer);
}

The sole responsibility of an UnwrappingMapSerializer is making MapSerializer’s keySerializer an unwrapping serializer.

this._keySerializer = this._keySerializer.unwrappingSerializer(transformer);

This changes the result of TestUnwrappedMap171.testMapUnwrapSerialize

junit.framework.ComparisonFailure: 
Expected :{"map.test": 6}
Actual   :{"test":6}

As you can see unwrapping worked but the name transformer did not have any effect as StdKeySerializers do not have a rename concept as BeanPropertyWriters do.

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?