【问题标题】:Java: ObjectMapper changes value/structure of Pojo after request/responseJava:ObjectMapper 在请求/响应后更改 Pojo 的值/结构
【发布时间】:2021-12-14 15:20:07
【问题描述】:

在将响应转换为我的 DTO 之前,我可以看到它看起来像这样:

{
    "name": "HalloweenBundle2021",
    "pricing":
    {
        "VirtualCurrencyDto(physicalCurrency=EUR, coinId=null)":
        {
            "amount": 8.99,
            "discountAmount": 0
        },
        "VirtualCurrencyDto(physicalCurrency=USD, coinId=null)":
        {
            "amount": 9.99,
            "discountAmount": 0
        }
    }
}

这是正确的。

但是,在响应/实际转换之后,physicalCurrency 字段都为空,而 coinId 字段获得完整的地图值:

{
    "name": "HalloweenBundle2021",
    "pricing":
    {
        "VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))":
        {
            "amount": 8.99,
            "discountAmount": 0
        },
        "VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=USD, coinId=null))":
        {
            "amount": 9.99,
            "discountAmount": 0
        }
    }
}

到底发生了什么?

我的回复是:

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FooDto implements Serializable {
  private String name;
  private Map<VirtualCurrencyDto, PriceDto> pricing = new LinkedHashMap<>();
}

问题的关键是:

@Data
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class VirtualCurrencyDto implements Serializable {

  // both of these fields should not be present, only one or the other
  private DTOCurrency physicalCurrency; // an external enum
  private String coinId;

  public VirtualCurrencyDto(DTOCurrency physicalCurrency) {
    this.physicalCurrency = physicalCurrency;
  }

  public VirtualCurrencyDto(String coinId) {
    this.coinId = coinId;
  }
}

我的 objectMapper bean

  @Bean
  public ObjectMapper getObjectMapper() {
    return new ObjectMapper()
        .setSerializationInclusion(JsonInclude.Include.ALWAYS)
        .registerModule(new JsonNullableModule());
  }

并且正在通过MockMvc获取响应

  public FooDto getFooDto(String name) throws Exception {
    MvcResult result =
        mockMvc.perform(
                get("/foo/" + name))
            .andExpect(status().isOk())
            .andReturn();

    return objectMapper.readValue(
        result.getResponse().getContentAsString(), // this part is fine, when I look at the string value
        FooDto.class); // issue here, upon conversion
      }

我应该注意,在提出请求时也是如此,例如在请求到达我的控制器以获取PUT 之前,数据看起来很好,但是在控制器中接收到它后,数据就像上面一样搞砸了(来自GET 的响应)。

【问题讨论】:

    标签: java spring jackson objectmapper


    【解决方案1】:

    我的猜测是问题的存在是因为您有一个复杂的对象作为 pricing 映射的键。 JSON 中的映射由一个简单的 String 作为键表示,值可以是一个复杂的对象。这意味着 Jackson 必须找到一种方法将复杂的 VirtualCurrencyDto 对象序列化为简单的 String。执行此操作的唯一方法是通过toString() 方法,这就是您看到"VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))" 的原因。这是错误的本身,因为您应该使用具有简单String 表示的简单对象作为JSON 映射中的键。但接下来,我们将了解您为什么会出现这种奇怪的行为。

    Jackson 尝试将 "VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))" 反序列化为 VirtualCurrencyDto 对象。由于它只包含一个String 属性,我的猜测是它创建了一个VirtualCurrencyDto 的实例,其中"VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))" 作为coinId 的值,VirtualCurrencyDto 中唯一的String 属性,因此你的行为体验。

    这里有两个选择:

    1. 您查看模型类,以便使用简单对象作为pricing Map 的键。例如,DTOCurrency 将是一个很好的候选人。
    2. 您为VirtualCurrencyDto 创建了一个自定义反序列化程序,它能够解析String "VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))" 并创建该类的一个实例。为此,您需要扩展StdDeserializer,然后将其注册到您的ObjectMapper。类似于以下内容:
    SimpleModule module = new SimpleModule()
            .addDeserializer(VirtualCurrencyDto.class, new VirtualCurrencyDtoDeserializer());
    
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(module);
    

    我会选择第一个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-11-17
      • 2019-10-20
      • 1970-01-01
      • 1970-01-01
      • 2020-11-26
      • 2021-08-06
      • 2018-07-24
      相关资源
      最近更新 更多