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



我通过将域类对象转换为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对象映射器生成一个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的'Article'类,如这里和这里所述。

据我所知,我应该使用反射来达到这个目的,但也许这将是一个过度的。还有其他的想法或建议吗?

我想我应该实现一个自定义序列化器/反序列化器

当然,您可以通过扩展抽象类StdSerializer并提供其serialize()方法的实现来实现自定义序列化器。但这需要大量的底层代码,而且不够灵活。

另一种选择是为Article类型创建一个所谓的Converter,并通过@JsonSerialize注释的converter属性提供它。这里有一段引用自说明Converter用途的文档:

将使用哪个helper对象将类型转换为Jackson知道如何序列化的内容;要么是因为基类型不容易序列化,要么只是为了改变序列化。

换句话说,Converter的目标是将基类型转换为另一种更容易序列化的类型。而Serializer的目的是通过JsonGenerator指导Jackson如何从给定对象生成JSON (当一个复杂的对象图和大量的更改需要完成时,这不是很方便)。这就是你如何区分两者的方法。

要为Article类型创建自定义转换器,我们需要扩展抽象类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 16record(您可以将其重新实现为普通class以及):

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

注意:LanguageAttribute的字段类型为*Map。这是一个单条目映射,旨在表示像"productInformation":"...","designation":"..."等不同的属性,并且使用@JsonAnyGetter进行注释,因为我们不希望属性"map"出现在结果JSON中(只需要其内容)。另一个选项(有些人可能会觉得更直观)是@JsonUnwrapped,但它不会在这里工作是因为这个众所周知的文档问题在撰写本文时仍未解决。

下面是一个简单的转换器,可以将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类型的转换器的实现,它有点复杂,因为这个转换器执行更多的转换,但它是可维护的,可以根据需要更改/扩展:

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作为实践练习。

相关内容

  • 没有找到相关文章

最新更新