Java: ObjectMapper 在 request/response 之后改变了 Pojo 的 value/structure
Java: ObjectMapper changes value/structure of Pojo after request/response
我可以看到在将响应转换为我的 DTO 之前它看起来像这样:
{
"name": "HalloweenBundle2021",
"pricing":
{
"VirtualCurrencyDto(physicalCurrency=EUR, coinId=null)":
{
"amount": 8.99,
"discountAmount": 0
},
"VirtualCurrencyDto(physicalCurrency=USD, coinId=null)":
{
"amount": 9.99,
"discountAmount": 0
}
}
}
这是正确的。
但是,在 response/actual 转换之后,两个 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
}
}
}
到底是怎么回事?
我的回复 Pojo 是:
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FooDto implements Serializable {
private String name;
private Map<VirtualCurrencyDto, PriceDto> pricing = new LinkedHashMap<>();
}
问题所在的 pojo 是:
@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
的响应)。
我的猜测是问题存在是因为您有一个复杂的对象作为 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
的值,唯一的 String
属性 在 VirtualCurrencyDto
中,因此您遇到的行为。
这里有两个选择:
- 您检查了您的模型 classes,以便使用一个简单的对象作为
pricing
地图的键。例如,DTOCurrency
将是一个很好的候选人。
- 您为
VirtualCurrencyDto
创建了一个自定义反序列化器,它能够解析 String
"VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))"
并创建 class 的实例。为此,您需要扩展 StdDeserializer
,然后在您的 ObjectMapper
中注册它。类似于以下内容:
SimpleModule module = new SimpleModule()
.addDeserializer(VirtualCurrencyDto.class, new VirtualCurrencyDtoDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
我会选择第一个。
我可以看到在将响应转换为我的 DTO 之前它看起来像这样:
{
"name": "HalloweenBundle2021",
"pricing":
{
"VirtualCurrencyDto(physicalCurrency=EUR, coinId=null)":
{
"amount": 8.99,
"discountAmount": 0
},
"VirtualCurrencyDto(physicalCurrency=USD, coinId=null)":
{
"amount": 9.99,
"discountAmount": 0
}
}
}
这是正确的。
但是,在 response/actual 转换之后,两个 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
}
}
}
到底是怎么回事?
我的回复 Pojo 是:
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class FooDto implements Serializable {
private String name;
private Map<VirtualCurrencyDto, PriceDto> pricing = new LinkedHashMap<>();
}
问题所在的 pojo 是:
@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
的响应)。
我的猜测是问题存在是因为您有一个复杂的对象作为 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
的值,唯一的 String
属性 在 VirtualCurrencyDto
中,因此您遇到的行为。
这里有两个选择:
- 您检查了您的模型 classes,以便使用一个简单的对象作为
pricing
地图的键。例如,DTOCurrency
将是一个很好的候选人。 - 您为
VirtualCurrencyDto
创建了一个自定义反序列化器,它能够解析String
"VirtualCurrencyDto(physicalCurrency=null, coinId=VirtualCurrencyDto(physicalCurrency=EUR, coinId=null))"
并创建 class 的实例。为此,您需要扩展StdDeserializer
,然后在您的ObjectMapper
中注册它。类似于以下内容:
SimpleModule module = new SimpleModule()
.addDeserializer(VirtualCurrencyDto.class, new VirtualCurrencyDtoDeserializer());
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(module);
我会选择第一个。