json Jackson-来自第三方库的嵌入式对象的自定义序列化

wbrvyc0a  于 2022-12-15  发布在  其他
关注(0)|答案(1)|浏览(110)

我将域类对象转换为JSON格式,将它们存储到文档数据库中。在存储之前,我希望对特定自定义类型LocalizedTexts的所有属性应用转换,并将它们作为条目存储到平面List/Array中。
为了更好地理解需求,下面是一些基本类:
这是主域类:

@Value
@NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@SuperBuilder(toBuilder = true)
@JsonDeserialize(builder = Article.ArticleBuilderImpl.class)
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Article extends AbstractEntityBase {
    
    @NonNull
    UUID id;
    
    @JsonProperty("iNo")
    Integer iNo;
    
    boolean isValid;
    
    @NonNull
    LocalizedTexts designation;
    
    @NonNull
    ReferenceArticleGroup articleGroup;
    
    Integer numberOfDecimalsSalesPrice;
    
    LocalizedTexts touchCaption;
    
    LocalizedTexts printText;
    
    LocalizedTexts productInformation;
    
    @Singular
    List<String> codes;
    ...
}

这是内部LocalizedTexts类:

@Builder(builderMethodName = "internalBuilder")
public class LocalizedTexts extends HashMap<Language, String> implements EntityBase {

    public LocalizedTexts() {

    }

    public LocalizedTexts(Map map) {
        putAll(map);
    }
}

最后,Language枚举类型:

public enum Language {
    AA("aa"),
    AB("ab"),
    AE("ae"),
    AF("af"),
    AK("ak"),
    ...
}

请注意LanguageLocalizedTexts是从另一个库导入的,因此我没有更改它们的权限。

标准Jackson对象Map器生成一个JSON文件,其结构如下:

{
    "id": "57bf6daf-4993-4c55-9b19-db6b3d5c9527",
    "iNo": 3,
    "isValid": true,
    "designation": {
        "de": "designation3DE localized designation3 designation",
        "en": "designation3EN localized designation3 designation"
    },
    "articleGroup": {
        "id": "8f6627b8-31d4-4e44-9374-6069571489f7",
        "type": "ArticleGroup"
      },
    "numberOfDecimalsSalesPrice": 2,
    "touchCaption": {
        "de": "touchCaption3DE localized touchCaption3 touchCaption",
        "en": "touchCaption3EN localized touchCaption3 touchCaption"
      },
    "printText": {
        "de": "printText3DE localized printText3 printText",
        "en": "printText3EN localized printText3 printText"
    },
    "productInformation": {
        "de": "productInformation3DE localized productInformation3 productInformation",
        "en": "productInformation3EN localized productInformation3 productInformation",
        "es": "productInformation3ES localized productInformation3 productInformation"
    },
    "codes": [
        "1231231231234",
        "2345678901234",
        "9999999999999",
        "1111111111111"
    ],
    ...
}

我想把我的对象序列化成一个json,格式如下:

{
    "id": "57bf6daf-4993-4c55-9b19-db6b3d5c9527",
    "iNo": 3,
    "isValid": true,
    "articleGroup": {
        "id": "8f6627b8-31d4-4e44-9374-6069571489f7",
        "type": "ArticleGroup"
    },
    "numberOfDecimalsSalesPrice": 2,
    "codes": [
        "1231231231234",
        "2345678901234",
        "9999999999999",
        "1111111111111"
    ],
    "translation": [
        {
            "productInformation": "productInformation3DE localized productInformation3 productInformation",
            "language": "german"
        },
        {
            "productInformation": "productInformation3EN localized productInformation3 productInformation",
            "language": "english"
        },
        {
            "productInformation": "productInformation3ES localized productInformation3 productInformation",
            "language": "spanish"
        },
        {
            "touchCaption": "touchCaption3DE localized touchCaption3 touchCaption Bildbeschriftung",
            "language": "german"
        },
        {
            "touchCaption": "touchCaption3EN localized touchCaption3 touchCaption Caption",
            "language": "english"
        },
        {
            "designation": "designation3DE localized designation3 designation",
            "language": "german"
        },
        {
            "designation": "designation3EN localized designation3 designation",
            "language": "english"
        }
    ],
    ...
}


所以我想提取所有的LocalizedTexts条目对,并将它们添加到一个数组中,如上所示。当从数据库阅读回时,我想实现相反的转换,并将上面的JSON反序列化到我原来的域类中。
我想我应该实现一个自定义的序列化器/反序列化器,并将其注册到我的ObjectMapper for 'Article'类(如此处所述)和here中。
据我所知,我应该使用反思的目的,但也许这将是一个矫枉过正。任何其他的想法或建议?

np8igboo

np8igboo1#

我想我应该实现一个自定义的序列化器/反序列化器
当然,可以通过扩展抽象类StdSerializer并提供其serialize()方法的实现来实现自定义Serializer,但这需要大量底层代码,而且不灵活。
另一种选择是为Article类型创建一个所谓的Converter,并通过@JsonSerialize注解的converter属性提供它。以下是解释Converter用途的文档中的一段引文:
使用哪个帮助对象将类型转换为Jackson知道如何序列化的对象;这可能是因为基类型无法轻松序列化,也可能只是为了更改序列化。
换句话说,Converter的目标是将基本类型转换为另一种更容易序列化的类型。而Serializer的目的是通过JsonGenerator指示Jackson如何从给定的对象生成JSON(* 这在复杂的对象图中不是很方便,需要做很多更改 *)。这就是你如何区分两者的方法。
要为Article类型创建自定义Converter,我们需要扩展抽象类StdConverter并覆盖其convert()方法,期望Article的示例并返回一个不同的对象,该对象可以顺利地反序列化。因此,首先,让我们创建此类型,用于存储根据需要构建的LocalizedTexts类型字段中的数据。
我们把这个类型叫做ArticleWrapper

@Builder
@Getter
@Setter
public class ArticleWrapper {
    @NonNull
    private UUID id;
    @JsonProperty("iNo")
    private Integer iNo;
    private boolean isValid;
    @NonNull
    private ReferenceArticleGroup articleGroup;
    private Integer numberOfDecimalsSalesPrice;
    @Singular
    private List<String> codes;
    
    private List<LanguageAttribute> translation; // all the data from LocalizedTexts is being stored here
}

它还需要一个名为LanguageAttribute的自定义类型来表示"{ "productInformation":"...", "language":"english" }"之类的信息,我将其定义为Java 16 record(* 您也可以将其重新实现为普通的class *):

public record LanguageAttribute(
    @JsonAnyGetter
    Map<String, String> map,
    @JsonProperty
    @JsonSerialize(converter = LanguageConverter.class)
    Language language) {}

注意:LanguageAttribute有一个类型为 * Map * 的字段。它是一个单条目Map,用于表示不同的属性,如 * "productInformation":"...""designation":"..." * 等。它被注解为 * @JsonAnyGetter *,因为我们不希望属性 * "map" * 出现在生成的JSON中(只需要它的内容)。另一个选项(有些人可能会觉得更直观)是 * @JsonUnwrapped *,但它在这里不起作用,因为这个众所周知的documented issue在编写本文时仍然没有得到解析。

下面是一个简单的转换器,它可以将Language枚举序列化为它的小写属性(***注意:**将用于访问小写语言名称的方法更改为正确的方法 *):

public class LanguageConverter extends StdConverter<Language, String> {

    @Override
    public String convert(Language language) {
        return language.getLangName(); // use the proper method here to access the lowercase enum property
    }
}

最后,这里是Article类型的Converter实现,它稍微复杂一点,因为此Converter执行更多转换,但它是可维护的,可以根据需要进行更改/扩展:

public class ArticleConverter extends StdConverter<Article, ArticleWrapper> {
    public ArticleConverter() throws JsonProcessingException {
    }

    @Override
    public ArticleWrapper convert(Article article) {
        
        List<LanguageAttribute> translations = getTranslations(article);
        
        return ArticleWrapper.builder()
            .id(article.getId())
            .iNo(article.getINo())
            .isValid(article.isValid())
            .articleGroup(article.getArticleGroup())
            .numberOfDecimalsSalesPrice(article.getNumberOfDecimalsSalesPrice())
            .translation(translations)
            .build();
    }
    
    private List<LanguageAttribute> getTranslations(Article article) {
        
        return Stream.of(
                toLanguageAttribute(article.getDesignation(), "designation"),
                toLanguageAttribute(article.getTouchCaption(), "touchCaption"),
                toLanguageAttribute(article.getPrintText(), "printText"),
                toLanguageAttribute(article.getProductInformation(), "productInformation")
            )
            .flatMap(Function.identity())
            .toList();
    }
    
    private Stream<LanguageAttribute> toLanguageAttribute(LocalizedTexts texts, String attribute) {
        return texts.entrySet().stream()
            .map(entry -> new LanguageAttribute(
                Map.of(attribute, entry.getValue()), entry.getKey())
            );
    }
}

免责声明:

  • 我已经测试了上面介绍的解决方案,以确保生成所需的JSON形状,但由于信息不是100%完全的,普通的复制粘贴可能不起作用,最有可能的是需要一些更改。

如果还需要定制反序列化的过程,我将把它留给OP/reader作为实践练习。

相关问题