Skip to content
New issue

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

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

Already on GitHub? Sign in 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working fixed
Projects
None yet
Development

No branches or pull requests

2 participants