Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

[BUG] Fix support to @JsonSubTypes #2548

Closed
jorgerod opened this issue May 9, 2024 · 3 comments
Closed

[BUG] Fix support to @JsonSubTypes #2548

jorgerod opened this issue May 9, 2024 · 3 comments
Labels
bug Something isn't working fixed
Milestone

Comments

@jorgerod
Copy link

jorgerod commented May 9, 2024

Hello

I have the following model with @JsonSubTypes (generated with openapi-generator):

package com.jorge;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import jakarta.validation.constraints.NotNull;

import java.io.Serializable;
import java.util.Objects;

/**
 * AnimalDTO
 */

@JsonIgnoreProperties(
  value = "vehicle_type", // ignore manually set animal_type, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the animal_type to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "vehicle_type", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = CarDTO.class, name = "Car"), 
        @JsonSubTypes.Type(value = CycleDTO.class, name = "Cycle"),
})
public class VehicleDTO implements Serializable {

  private static final long serialVersionUID = 1L;

  private String vehicleType;

  public VehicleDTO() {
    super();
  }

  /**
   * Constructor with only required parameters
   */
  public VehicleDTO(String vehicleType) {
    this.vehicleType = vehicleType;
  }

  public VehicleDTO vehicleType(String vehicleType) {
    this.vehicleType = vehicleType;
    return this;
  }

  /**
   * Get animalType
   * @return animalType
  */
  @NotNull 
  @JsonProperty("vehicle_type")
  public String getVehicleType() {
    return vehicleType;
  }

  public void setVehicleType(String vehicleType) {
    this.vehicleType = vehicleType;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    VehicleDTO vehicle = (VehicleDTO) o;
    return Objects.equals(this.vehicleType, vehicle.vehicleType);
  }

  @Override
  public int hashCode() {
    return Objects.hash(vehicleType);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class VehicleDTO {\n");
    sb.append("    vehicleType: ").append(toIndentedString(vehicleType)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}
package com.jorge;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import jakarta.annotation.Generated;

import java.io.Serializable;
import java.util.Objects;

/**
 * CatDTO
 */


@JsonTypeName("Car")
@Generated(value = "com.inditex.amigafwk.common.rest.server.codegen.AmigaSpringCodegen", date = "2024-05-08T17:52:17.338191116+02:00[Europe/Madrid]", comments = "Generator version: 7.5.0")
public class CarDTO extends VehicleDTO implements Serializable {

  private static final long serialVersionUID = 1L;


  private Integer age;

  public CarDTO() {
    super();
  }

  /**
   * Constructor with only required parameters
   */
  public CarDTO(String vehicleType) {
    super(vehicleType);
  }

  public CarDTO age(Integer age) {
    this.age = age;
    return this;
  }

  /**
   * Get age
   * @return age
  */
  
  @JsonProperty("age")
  public Integer getAge() {
    return age;
  }

  public void setAge(Integer age) {
    this.age = age;
  }


  public CarDTO vehicleType(String vehicleType) {
    super.vehicleType(vehicleType);
    return this;
  }
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    CarDTO car = (CarDTO) o;
    return      Objects.equals(this.age, car.age) &&
        super.equals(o);
  }

  @Override
  public int hashCode() {
    return Objects.hash(age, super.hashCode());
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class CaDTO {\n");
    sb.append("    ").append(toIndentedString(super.toString())).append("\n");
    sb.append("    age: ").append(toIndentedString(age)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

Running with jackson, it goes well. With fastjson2, it fails.

package com.jorge;

import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class JsonConfigTest {

    @Test
    void fastJson() {
        //given
        CarDTO carDTO = new CarDTO();
        carDTO.setAge(10);

        //when
        String jsonString = JSON.toJSONString(carDTO);
        VehicleDTO vehicleDTO = JSON.parseObject(jsonString, VehicleDTO.class);

        //then
        assertThat(vehicleDTO.getVehicleType()).isEqualTo("Car"); //KO
    }

    @Test
    void jackson() throws JsonProcessingException {
        //given
        ObjectMapper objectMapper = new ObjectMapper();
        CarDTO carDTO = new CarDTO();
        carDTO.setAge(10);

        //when
        String jsonString = objectMapper.writeValueAsString(carDTO);
        VehicleDTO vehicleDTO = objectMapper.readValue(jsonString, VehicleDTO.class);

        //then
        assertThat(vehicleDTO.getVehicleType()).isEqualTo("Car");
    }
}
@jorgerod jorgerod added the bug Something isn't working label May 9, 2024
@wenshao wenshao added this to the 2.0.50 milestone May 10, 2024
@wenshao wenshao added the fixed label May 10, 2024
@jorgerod
Copy link
Author

Hi @wenshao

Thank you very much for the quick response. I have tried it and that case works for me.
But I have another case a bit more complex and it doesn't work. It is an object that has a field which is the one that has the @JsonSubTypes

Model:

package com.jorge.mynamespacerestserviceopenapi.dto;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.inditex.mynamespacerestserviceopenapi.dto.FruitDTO;
import java.io.Serializable;
import java.time.OffsetDateTime;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;


import java.util.*;
import jakarta.annotation.Generated;

/**
 * StoreDTO
 */

@JsonTypeName("store")
public class StoreDTO implements Serializable {

  private static final long serialVersionUID = 1L;

  private String storeId;

  private FruitDTO item;

  public StoreDTO storeId(String storeId) {
    this.storeId = storeId;
    return this;
  }

  /**
   * The unique ID of the fruit
   * @return storeId
  */
  
  @JsonProperty("storeId")
  public String getStoreId() {
    return storeId;
  }

  public void setStoreId(String storeId) {
    this.storeId = storeId;
  }

  public StoreDTO item(FruitDTO item) {
    this.item = item;
    return this;
  }

  /**
   * Get item
   * @return item
  */
  @Valid 
  @JsonProperty("item")
  public FruitDTO getItem() {
    return item;
  }

  public void setItem(FruitDTO item) {
    this.item = item;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    StoreDTO store = (StoreDTO) o;
    return Objects.equals(this.storeId, store.storeId) &&
        Objects.equals(this.item, store.item);
  }

  @Override
  public int hashCode() {
    return Objects.hash(storeId, item);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class StoreDTO {\n");
    sb.append("    storeId: ").append(toIndentedString(storeId)).append("\n");
    sb.append("    item: ").append(toIndentedString(item)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}
package com.jorge.mynamespacerestserviceopenapi.dto;

import java.net.URI;
import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.jorge.mynamespacerestserviceopenapi.dto.AppleDTO;
import com.jorge.mynamespacerestserviceopenapi.dto.BananaDTO;
import java.math.BigDecimal;
import java.io.Serializable;
import java.time.OffsetDateTime;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;


import java.util.*;
import jakarta.annotation.Generated;


@JsonIgnoreProperties(
  value = "kind", // ignore manually set kind, it will be automatically generated by Jackson during serialization
  allowSetters = true // allows the kind to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "kind", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = AppleDTO.class, name = "apple"),
  @JsonSubTypes.Type(value = BananaDTO.class, name = "banana")
})
public interface FruitDTO extends Serializable {
    public String getKind();
}
package com.jorge.mynamespacerestserviceopenapi.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import jakarta.annotation.Generated;
import jakarta.validation.Valid;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Objects;

/**
 * BananaDTO
 */

@JsonTypeName("banana")
public class BananaDTO implements Serializable, FruitDTO {

  private static final long serialVersionUID = 1L;

  private String kind;

  private BigDecimal count;

  public BananaDTO kind(String kind) {
    this.kind = kind;
    return this;
  }

  /**
   * Get kind
   * @return kind
  */
  
  @JsonProperty("kind")
  public String getKind() {
    return kind;
  }

  public void setKind(String kind) {
    this.kind = kind;
  }

  public BananaDTO count(BigDecimal count) {
    this.count = count;
    return this;
  }

  /**
   * Get count
   * @return count
  */
  @Valid 
  @JsonProperty("count")
  public BigDecimal getCount() {
    return count;
  }

  public void setCount(BigDecimal count) {
    this.count = count;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    BananaDTO banana = (BananaDTO) o;
    return Objects.equals(this.kind, banana.kind) &&
        Objects.equals(this.count, banana.count);
  }

  @Override
  public int hashCode() {
    return Objects.hash(kind, count);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class BananaDTO {\n");
    sb.append("    kind: ").append(toIndentedString(kind)).append("\n");
    sb.append("    count: ").append(toIndentedString(count)).append("\n");
    sb.append("}");
    return sb.toString();
  }

  /**
   * Convert the given object to string with each line indented by 4 spaces
   * (except the first line).
   */
  private String toIndentedString(Object o) {
    if (o == null) {
      return "null";
    }
    return o.toString().replace("\n", "\n    ");
  }
}

Test

class JsonConfigTest {

    @Test
    void fastJson() {
        //given
        StoreDTO storeDTO = new StoreDTO();
        BananaDTO banana = new BananaDTO();
        banana.setCount(BigDecimal.valueOf(10));
        storeDTO.setItem(banana);

        //when
        String jsonString = JSON.toJSONString(storeDTO);
        StoreDTO store = JSON.parseObject(jsonString, StoreDTO.class);

        //then
        assertThat(store.getItem().getKind()).isEqualTo("banana");
    }

    @Test
    void jackson() throws JsonProcessingException {
        //given
        ObjectMapper objectMapper = new ObjectMapper();

        StoreDTO storeDTO = new StoreDTO();
        BananaDTO banana = new BananaDTO();
        banana.setCount(BigDecimal.valueOf(10));
        storeDTO.setItem(banana);

        //when
        String jsonString = objectMapper.writeValueAsString(storeDTO);
        StoreDTO store = objectMapper.readValue(jsonString, StoreDTO.class);

        //then
        assertThat(store.getItem().getKind()).isEqualTo("banana");
    }
}

@wenshao
Copy link
Member

wenshao commented May 11, 2024

https://oss.sonatype.org/content/repositories/snapshots/com/alibaba/fastjson2/fastjson2/2.0.50-SNAPSHOT/
The problem has been fixed. Please help to verify it with version 2.0.50-SNAPSHOT.

@wenshao
Copy link
Member

wenshao commented May 12, 2024

https://github.com/alibaba/fastjson2/releases/tag/2.0.50
2.0.50 has been released, please use the new version

@wenshao wenshao closed this as completed May 12, 2024
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

2 participants