Flat JSON in Jackson for class/record with one field

Viewed 881

I have a Java record with one field only:

public record AggregateId(UUID id) {}

And a class with the AggregateId field (other fields removed for readability)

public class Aggregate {

    public final AggregateId aggregateId;

    @JsonCreator
    public Aggregate(
            @JsonProperty("aggregateId") AggregateId aggregateId
    ) {
        this.aggregateId = aggregateId;
    }
}

The implementation above serialize and deserialize JSON with given example:

ObjectMapper objectMapper = new ObjectMapper();
String content = """
        {
           "aggregateId": {
                "id": "3f61aede-83dd-4049-a6ff-337887b6b807"
            }
        }
        """;
Aggregate aggregate = objectMapper.readValue(content, Aggregate.class);
System.out.println(objectMapper.writeValueAsString(aggregate));

How could I change Jackson config to replace JSON by that:

{
    "aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
}

without giving up a separate class for AggregateId and access through fields, without getters?

I tried @JsonUnwrapper annotation, but this caused throws

Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
    Invalid type definition for type `X`: 
        Cannot define Creator parameter as `@JsonUnwrapped`: combination not yet supported at [Source: (String)"{
            "aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
        }"

or

Exception in thread "X" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: 
    Cannot define Creator property "aggregateId" as `@JsonUnwrapped`: 
        combination not yet supported at [Source: (String)"{
            "aggregateId": "3f61aede-83dd-4049-a6ff-337887b6b807"
        }"

Jackson version: 2.13.1

dependencies {
    compile "com.fasterxml.jackson.core:jackson-annotations:2.13.1"
    compile "com.fasterxml.jackson.core:jackson-databind:2.13.1"
}

Of course, it's possible with a custom serializer/deserializer, but I'm looking for an easier solution because I have many different classes with a similar issue.

1 Answers

The combination of @JsonUnwrapped and @JsonCreator is not supported yet, so we can generate a solution like this:

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import java.util.UUID;

public class AggregateTest {

    static record AggregateId(@JsonProperty("aggregateId") UUID id) {}

    static class Aggregate {

        @JsonUnwrapped
        @JsonProperty(access = JsonProperty.Access.READ_ONLY)
        public final AggregateId _aggregateId;
        public final String otherField;

        @JsonCreator
        public Aggregate(@JsonProperty("aggregateId") UUID aggregateId,
                         @JsonProperty("otherField") String otherField) {
            this._aggregateId = new AggregateId(aggregateId);
            this.otherField = otherField;
        }
    }

    public static void main(String[] args) throws JsonProcessingException {
        String rawJson =
            "{\"aggregateId\": \"1f61aede-83dd-4049-a6ff-337887b6b807\"," +
                    "\"otherField\": \"İsmail Y.\"}";
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        Aggregate aggregate = objectMapper
                .readValue(rawJson, Aggregate.class);
        System.out.println(objectMapper
                .writeValueAsString(aggregate));
    }
}

Here we briefly get rid of the @JsonUnwrapped field.

We get the UUID with the name aggregateId and create an AggregateId record.

Detailed explanations about it:

Related