【问题标题】:@Jacksonized deserialization doesn't work with mixins - "no String-argument constructor/factory method to deserialize from String value"@Jacksonized 反序列化不适用于 mixins -“没有字符串参数构造函数/工厂方法从字符串值反序列化”
【发布时间】:2021-07-08 21:45:46
【问题描述】:

我正在尝试从包含地图的非标准表示形式作为列之一的 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: 不能 构造java.util.LinkedHashMap 的实例(尽管至少有一个 Creator 存在):没有字符串参数构造函数/工厂方法 从 [来源: (字符串读取器);行:2,列:53](通过参考链: 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,所以我很困惑为什么这不起作用。解决此问题的下一步是什么?

【问题讨论】:

  • 将mixin添加到MyObjectBuilder.class而不是MyObject.class时是否有效?
  • @JanRieke 使用 csvMapper.addMixIn(MyObject.MyObjectBuilder.class, MyObjectDeserializerMixin.class); 不起作用,不。
  • 奇怪。我只是将您的示例 c&p'ed 到我的 IDE 中,当我将 mixin 添加到构建器时它可以工作。您在此处的示例代码中是否省略了某些内容?使用 Lombok 1.18.20 和 Jackson 2.12.2。
  • 我确实尝试对其进行了一些清理 - 例如,MyObject 中有更多字段,并且名称不同..让我尝试将我的代码简化为这个孤立的示例.. 另外我使用的 jackson 和 lombok 版本是 jackson 2.10.3 和 lombok 1.18.16
  • 由于该解决方案对我有用,我添加了一个答案。让我们继续讨论,让它适用于您的案例。

标签: java csv jackson lombok


【解决方案1】:

当使用构建器反序列化时,Jackson 只考虑构建器类上的注释,而不考虑实际类上的注释来反序列化。 Lombok 的 @Jacksonized 通过自动将所有相关注释复制到构建器类及其设置器方法来帮助您。

但是,Lombok 只能通过静态存在于类上的注释来做到这一点。任何来自 mixin 的动态注释都不能被复制,因为 Lombok 不知道它们。 您可以将@JsonDeserialize 放在实际类的mapField 上,以便Lombok 能够将其复制到构建器。但这显然与 mixins 的目的背道而驰。

幸运的是,有更好的方法。您还可以将 mixin 添加到构建器类中,如下所示:

csvMapper.addMixIn(MyObjectBuilder.class, MyObjectDeserializerMixin.class);

严格来说,您不再需要 MyObject 上的 mixin。但是如果你也序列化了,并且mixin中有与序列化相关的注解,你应该将mixin添加到两者中。

但是,就您而言,这还不够,因为您使用的是@Builder.Default。使用该注释,Lombok 在构建器中创建一个名为 mapField$value 的字段。 Jackson 不会将您的 mixin 中的字段 mapField(及其注释)匹配到该字段,因为它们的名称不同。 您可以通过在 mixin 中定义和注释 setter 方法来解决这个问题:

public abstract class MyObjectDeserializerMixin {
    @JsonDeserialize(using = StringToMapDeserializer.class)
    public abstract void mapField(Map<String, Float> mapField);
}

您可以在这里使用构建器的实际返回类型,但void 也足够了。由于 @JsonDeserialize 仅用于反序列化目的,您可以安全地从 mixin 类中删除 mapField 及其注解。

使用 Lombok 1.18.20 和 Jackson 2.12.2 测试。

【讨论】:

  • 好的.. 我在清理示例时犯了一个错误。我会更新我的问题。主要区别在于我在MyObjectmapField 上使用@Builder.DefaultmapField = new HashMap&lt;&gt;();
  • 我将答案扩展为也适用于@Builder.Default 案例。
  • 非常感谢。
猜你喜欢
  • 1970-01-01
  • 2017-03-03
  • 2018-09-09
  • 1970-01-01
  • 2019-12-31
  • 2017-12-23
  • 2021-09-22
  • 2019-05-15
  • 2017-04-20
相关资源
最近更新 更多