2

I have this situation:

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
@JsonSubTypes({
        @JsonSubTypes.Type(value = FlyingCar.class, name = "flying_car"),
        @JsonSubTypes.Type(value = WaterProofPhone.class, name = "water_proof_phone"),
        @JsonSubTypes.Type(value = ElectricBicycle.class, name = "electric_bicycle")
})
public abstract class Product {
}

and every subclass is defined like:

@JsonTypeName("flying_car")
public class FlyingCar extends Product {
     private double verticalSpeed; 
     private String name;
}

When I serialize the class below, I would like to not include a product property in the json:

public class Cart {
   private long id;
   private LocalDateTime date;
   private Product product;
}

Example serializing this configuration:

Product product = new FlyingCar(123.5,"StarShip");
Cart cart = new Cart();
cart.setProduct(product);

String json = objectMapper.writeValueAsString(cart);

Produces this json:

{
  "id":..,
  "product": { <--- How can I remove this wrapper ?
    "flying_car":{
      "vertical_speed": 123.5,
      "name": "StarShip"
    }
  }
}

How to simply have a json like this, without the supertype wrapper?

{
  "id":..,
  "flying_car":{
      "vertical_speed": 123.5,
      "name": "StarShip"
   }
}

I tried the @JsonUnwrapped on product but it does not work as expected.

Thanks for your help

akuma8
  • 4,160
  • 5
  • 46
  • 82
  • From javadoc of [@JsonUnwrapped](https://fasterxml.github.io/jackson-annotations/javadoc/2.13/com/fasterxml/jackson/annotation/JsonUnwrapped.html) - **Will not work with polymorphic type handling ("polymorphic deserialization")**. I guess your only option is writing custom serializer/deserializer. – Chaosfire Jun 30 '22 at 11:48
  • Thanks for the suggestion. A custom serializer for the specific property `product` or for the class `Cart`? – akuma8 Jun 30 '22 at 11:55
  • Probably for Cart will be easier, but both look complicated to me. Simplest solution i can think of would be to change type info inclusion to something else, for example `JsonTypeInfo.As.PROPERTY`, like this there won't be any wrapper object in the first place. – Chaosfire Jun 30 '22 at 12:22
  • I already tried `JsonTypeInfo.As.PROPERTY` but the `product` property is still there with a `@type` field. – akuma8 Jun 30 '22 at 12:35
  • Yes, `product` is there, but there is no nested object inside it, only the properties + the type property. – Chaosfire Jun 30 '22 at 12:58
  • I found another solution using `@JsonAnyGetter`, it avoids creating an custom serializer – akuma8 Jun 30 '22 at 13:55

3 Answers3

1

As mentioned in the comments, you have to use a custom serializer to implement that.

The following serializer implementation should work as expected.

public class CustomSerializer extends JsonSerializer<Cart> {
    @Override
    public void serialize(Cart cart, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        final Object product = cart.getProduct();
        Class<?> responseClass = product.getClass();
        JavaType responseJavaType = serializers.constructType(responseClass);
        gen.writeStartObject();
        gen.writeFieldName(serializers.findTypeSerializer(responseJavaType).getTypeIdResolver().idFromValue(product));
        serializers.findValueSerializer(responseClass).serialize(product, gen, serializers);
        /* Here you must manually serialize other properties */
        gen.writeObjectField("id", cart.getId());
        gen.writeEndObject();
    }


}

And you need to set this seriliazer for your Cart class :

@JsonSerialize(using = CustomSerializer.class)
public class Cart {
...

}
Nemanja
  • 3,295
  • 11
  • 15
1

To complete @Nemanja's answer, I found a simpler solution using @JsonAnyGetter:

public class Cart {
   private long id;
   private LocalDateTime date;
   @JsonIgnore
   private Product product;

   @JsonAnyGetter
   Map<String, Product> determineProduct(){
     if (this.product instanceof FlyingCar){
       return Map.of("flying_car", this.product);
     }
     ....other type checking
  }
}

It's a bit simpler and we don't have to define a custom serializer

akuma8
  • 4,160
  • 5
  • 46
  • 82
0

Had the exact problem. Used a slightly cleaner update to @akuma8's @JsonAnyGetter. Put @JsonTypeName on your polymorphic classes and read it in the getter for less code maintenance.

@JsonAnyGetter
public Map<String, Product> determineProduct() {
    JsonTypeName[] typeName = this.product.getClass().getDeclaredAnnotationsByType(JsonTypeName.class);
    if (typeName.length > 0) {
        return ImmutableMap.of(typeName[0].value(), this.product);
    }
    return Collections.emptyMap();
}
PQuinn
  • 992
  • 6
  • 11