@Jacksonized 反序列化不适用于混合 - "no String-argument constructor/factory method to deserialize from String value"
@Jacksonized deserialization doesn't work with mixins - "no String-argument constructor/factory method to deserialize from String value"
我正在尝试从包含地图的非标准表示形式的 csv 字符串中反序列化 MyObject
的数组(它通过 Lombok 和 @Jacksonized
使用构建器模式)列。
我的对象:
@JsonPropertyOrder({
"strField",
"mapField",
})
@Getter
@Jacksonized
@Builder(toBuilder = true)
@EqualsAndHashCode
@ToString
public class MyObject {
private final String strField;
@Builder.Default
private final Map<String, Float> mapField = new HashMap<>();
}
具有非标准 mapField
表示的 csv 示例:
strField,mapField
abc,"key1=2.0;key2=3.0"
我正在使用 mixin 尝试在不重写整个对象的情况下实现此目的:
@JsonPropertyOrder({
"strField",
"mapField",
})
public abstract class MyObjectDeserializerMixin {
@JsonDeserialize(using = StringToMapDeserializer.class)
private Map<String, Float> mapField;
}
..正如您在上面看到的,它指向自定义反序列化器:
public class StringToMapDeserializer extends JsonDeserializer<Map<String, Float>> {
@Override
public Map<String, Float> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String csvFormattedMap = jsonParser.getText().trim();
return Arrays.stream(csvFormattedMap.split(";"))
.map(keyValue -> keyValue.split("="))
.collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> Float.parseFloat(keyValue[1])));
}
}
并且,总而言之,我正在配置和使用我的 CsvMapper
,如下所示:
CsvMapper csvMapper = new CsvMapper();
csvMapper.addMixIn(MyObject.class, MyObjectDeserializerMixin.class);
CsvSchema csvSchema = csvMapper
.schemaFor(MyObject.class)
.withHeader();
ObjectReader csvReader = csvMapper.readerFor(MyObject.class).with(csvSchema);
List<MyObject> myObjects = csvReader.<MyObject>readValues(theCsvString).readAll();
但是,我遇到了以下异常:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of java.util.LinkedHashMap
(although at least one
Creator exists): no String-argument constructor/factory method to
deserialize from String value ('key1=2.0;key2=3.0') at [Source:
(StringReader); line: 2, column: 53] (through reference chain:
myPackage.MyObject$MyObjectBuilder["mapField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromString(ValueInstantiator.java:258)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:357)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:193)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1261)
...
堆栈跟踪似乎正在尝试使用 BuilderBasedDeserializer
,它试图对地图使用 MapDeserializer.java
,因此它似乎没有意识到我的自定义反序列化器。我使用了非常相似的工作流程和自定义 serializer 来编写相同的 csv,所以我很困惑为什么这不起作用。解决此问题的下一步是什么?
当使用构建器反序列化时,Jackson 只考虑构建器 class 上的注释,而不考虑实际 class 上的注释进行反序列化。 Lombok 的 @Jacksonized
通过自动将所有相关注释复制到构建器 class 及其 setter 方法来帮助您。
但是,Lombok 只能通过 class 上静态存在的注释来做到这一点。无法复制来自混入的任何动态注释,因为 Lombok 不知道它们。
您可以将 @JsonDeserialize
放到实际 class 的 mapField
上,以便 Lombok 能够将其复制到构建器。但这显然与 mixin 的目的背道而驰。
幸运的是,有更好的方法。您还可以将 mixin 添加到构建器 class 中,如下所示:
csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);
严格来说,您不再需要 MyObject
上的 mixin。但是如果你也序列化了,而且mixin里面有序列化相关的注解,你应该把mixin都加上。
但是,在您的情况下,这还不够,因为您正在使用 @Builder.Default
。使用该注释,Lombok 在构建器中创建一个名为 mapField$value
的字段。 Jackson 不会将您的 mixin 中的字段 mapField
(及其注释)匹配到该字段,因为它们的名称不同。
您可以通过在混合中定义和注释 setter 方法来解决这个问题:
public abstract class MyObjectDeserializerMixin {
@JsonDeserialize(using = StringToMapDeserializer.class)
public abstract void mapField(Map<String, Float> mapField);
}
您可以在此处使用构建器的实际 return 类型,但 void
也足够了。由于 @JsonDeserialize
仅用于反序列化目的,您可以安全地从 mixin class.
中删除 mapField
及其注释
使用 Lombok 1.18.20 和 Jackson 2.12.2 进行测试。
我正在尝试从包含地图的非标准表示形式的 csv 字符串中反序列化 MyObject
的数组(它通过 Lombok 和 @Jacksonized
使用构建器模式)列。
我的对象:
@JsonPropertyOrder({
"strField",
"mapField",
})
@Getter
@Jacksonized
@Builder(toBuilder = true)
@EqualsAndHashCode
@ToString
public class MyObject {
private final String strField;
@Builder.Default
private final Map<String, Float> mapField = new HashMap<>();
}
具有非标准 mapField
表示的 csv 示例:
strField,mapField
abc,"key1=2.0;key2=3.0"
我正在使用 mixin 尝试在不重写整个对象的情况下实现此目的:
@JsonPropertyOrder({
"strField",
"mapField",
})
public abstract class MyObjectDeserializerMixin {
@JsonDeserialize(using = StringToMapDeserializer.class)
private Map<String, Float> mapField;
}
..正如您在上面看到的,它指向自定义反序列化器:
public class StringToMapDeserializer extends JsonDeserializer<Map<String, Float>> {
@Override
public Map<String, Float> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
String csvFormattedMap = jsonParser.getText().trim();
return Arrays.stream(csvFormattedMap.split(";"))
.map(keyValue -> keyValue.split("="))
.collect(Collectors.toMap(keyValue -> keyValue[0], keyValue -> Float.parseFloat(keyValue[1])));
}
}
并且,总而言之,我正在配置和使用我的 CsvMapper
,如下所示:
CsvMapper csvMapper = new CsvMapper();
csvMapper.addMixIn(MyObject.class, MyObjectDeserializerMixin.class);
CsvSchema csvSchema = csvMapper
.schemaFor(MyObject.class)
.withHeader();
ObjectReader csvReader = csvMapper.readerFor(MyObject.class).with(csvSchema);
List<MyObject> myObjects = csvReader.<MyObject>readValues(theCsvString).readAll();
但是,我遇到了以下异常:
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of
java.util.LinkedHashMap
(although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('key1=2.0;key2=3.0') at [Source: (StringReader); line: 2, column: 53] (through reference chain: myPackage.MyObject$MyObjectBuilder["mapField"])
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1432)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1062)
at com.fasterxml.jackson.databind.deser.ValueInstantiator._createFromStringFallbacks(ValueInstantiator.java:371)
at com.fasterxml.jackson.databind.deser.ValueInstantiator.createFromString(ValueInstantiator.java:258)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:357)
at com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:29)
at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeSetAndReturn(MethodProperty.java:158)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.vanillaDeserialize(BuilderBasedDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.BuilderBasedDeserializer.deserialize(BuilderBasedDeserializer.java:193)
at com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1719)
at com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:1261)
...
堆栈跟踪似乎正在尝试使用 BuilderBasedDeserializer
,它试图对地图使用 MapDeserializer.java
,因此它似乎没有意识到我的自定义反序列化器。我使用了非常相似的工作流程和自定义 serializer 来编写相同的 csv,所以我很困惑为什么这不起作用。解决此问题的下一步是什么?
当使用构建器反序列化时,Jackson 只考虑构建器 class 上的注释,而不考虑实际 class 上的注释进行反序列化。 Lombok 的 @Jacksonized
通过自动将所有相关注释复制到构建器 class 及其 setter 方法来帮助您。
但是,Lombok 只能通过 class 上静态存在的注释来做到这一点。无法复制来自混入的任何动态注释,因为 Lombok 不知道它们。
您可以将 @JsonDeserialize
放到实际 class 的 mapField
上,以便 Lombok 能够将其复制到构建器。但这显然与 mixin 的目的背道而驰。
幸运的是,有更好的方法。您还可以将 mixin 添加到构建器 class 中,如下所示:
csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);
严格来说,您不再需要 MyObject
上的 mixin。但是如果你也序列化了,而且mixin里面有序列化相关的注解,你应该把mixin都加上。
但是,在您的情况下,这还不够,因为您正在使用 @Builder.Default
。使用该注释,Lombok 在构建器中创建一个名为 mapField$value
的字段。 Jackson 不会将您的 mixin 中的字段 mapField
(及其注释)匹配到该字段,因为它们的名称不同。
您可以通过在混合中定义和注释 setter 方法来解决这个问题:
public abstract class MyObjectDeserializerMixin {
@JsonDeserialize(using = StringToMapDeserializer.class)
public abstract void mapField(Map<String, Float> mapField);
}
您可以在此处使用构建器的实际 return 类型,但 void
也足够了。由于 @JsonDeserialize
仅用于反序列化目的,您可以安全地从 mixin class.
mapField
及其注释
使用 Lombok 1.18.20 和 Jackson 2.12.2 进行测试。