【问题标题】:Map to snake case while using a builder使用构建器时映射到蛇盒
【发布时间】:2021-10-24 00:38:15
【问题描述】:

我正在接收来自休息呼叫的 json 数据。密钥都在camel case

我可以从 rest 调用中很好地获取这些数据。但我希望将所有这些密钥转换为 snake case 因为那是
我发回给需要我回复的客户的版本。

在我的配置中,我有以下映射蛇的情况。

@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setObjectMapper(new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        .setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES));
    return converter;
}

如果我不明确使用@JsonProperty 并且坚持使用getterssetters 而不是builder,则此方法有效。
例如,如果我的 bean 以以下格式声明,这将有效并给我蛇盒。

@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Layout {
    private final String myBanner;
}

如果我使用构建器但不使用@JsonProperty,它将无法工作(不会从休息调用中捕获数据),如下所示。

@Getter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonDeserialize(builder = Layout.LayoutBuilder.class)
public class Layout {
    private final String myBanner;
}

这就是我现在所拥有的,但它是骆驼式的。我要蛇皮盒。

@Getter
@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonDeserialize(builder = Layout.LayoutBuilder.class)
public class Layout {

    @JsonProperty("myBanner")
    private final String myBanner;
}

我想坚持使用builder。因此问题是,有没有办法使用构建器并仍然获得snake case 中的值 我的回应。

或者,一种递归循环对象中所有字段的方法,包括嵌套对象,并将它们全部切换为蛇形?

来自rest调用的Json数据

{
    "mainData": {
        "groupData": "",
        "benefits": {
            "summary": {
                "title": "",
                "shortCopy": ""
            }
        },
        "simpleLayout": {
            "myBanner": "summary",
            "titles": [
                [
                    "",
                    ""
                ]
            ]
        },
        "maxLayout": {
            "myBanner": "summary",
            "titles": [
                [
                    ""
                ]
            ]
        }
    }
}

无法获取翻译方法的屏幕截图。

【问题讨论】:

    标签: java jackson lombok


    【解决方案1】:

    你可以使用@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)注解。

    也可以使用 Project Lombok

    例如,

    @Getter
    @Builder
    @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
    public class YourDto {
        private final String myBanner;
    
        public YourDto(@JsonProperty("myBanner")
                       @JsonAlias("my_banner") String myBanner) {
            this.myBanner = myBanner;
        }
    }
    

    上面的类可以反序列化下面的JSON,

    {   
        "myBanner": "My own banner.."
    }
    

    并序列化为以下 JSON

    {
        "my_banner": "My own banner.."
    }
    

    【讨论】:

    • 谢谢。我试过这个。相信没有@setter 它将无法工作。我不能有 setter,因为它使类可变。
    • 我已经更新了示例..检查并告诉我
    • 谢谢是一个选项。但是感觉不太可行,可以看到上面的 json 块有更多的字段,实际上 json 块实际上要大得多。以这种方式为所有嵌套的 bean 编写代码会非常繁重。
    • 我明白你的意思。实际上,如果您的 DTO 类具有多个属性,您可以跳过实现构造函数。如果 DTO 只有一个属性构造函数,就像示例一样是强制性的。 (即使构造函数是必须的,你也可以跳过 @JsonAlias("my_banner") 部分)
    【解决方案2】:

    TL;DR:扩展 Jackson 的 PropertyNamingStrategy 以不同方式进行双向翻译。

    这种方法使您的目标类(例如Layout)保持不变(没有添加构造函数,没有注释字段)。请参阅“示例”以进行复制。

    问题

    您在这里混合了很多配置选项:

    • PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES
    • @JsonProperty
    • @JsonDeserialize(builder = Layout.LayoutBuilder.class)

    1。 PropertyNamingStrategy 双向工作

    PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES 自 2.7 版起已弃用,并已替换为 SNAKE_CASE

    ⚠️ 它也可能被误解为从“camelCase”中的 JSON 输入字段到“snake_case”中的 JSON 输出字段的双向翻译

    但它实际上在 SNAKE_CASE 之间应用此属性名称转换 - 反序列化(JSON 输入)和序列化(JSON 输出)。

    所以在您的第一次尝试中(使用 builder 但没有 @JsonProperty)它不读取“camelCase”输入,因为期待“snake_case”,正如之前在 objectMapper 上配置的那样

    2。 JsonProperty 否决 PropertyNamingStrategy

    在您的第二次尝试中(使用 @JsonProperty("camelCase")),它确实读取了“camelCase”,但也写入了“camelCase”,因为字段注释覆盖了之前在 @987654346 上配置的 SNAKE_CASE 命名策略@。

    查看类似问题: Lombok with Jackson Deserializer from camelCase

    3。不可变需要构建器反序列化

    不可变意味着字段是final,不能与setter-deserialization一起使用。只有构造函数才能工作。因此,您决定使用 Lomboks @Builder 和 Jackson 的 @Deserialize(builder = Layout.LayoutBuilder.class)

    这使得类定义简单并且只包含一个字段列表。

    由于 Lombok 生成的构建器代码将原始字段名称用于构建方法和构造函数参数,因此任何使用 @JsonProperty 注释的字段和不同名称都不会考虑使用这些不同名称进行反序列化。

    为反序列化定义一个单独的构造函数(带有@JsonProperty注解的参数)很麻烦,并且意味着额外的维护工作。这就是我对rai's answer 的担忧。

    解决属性命名问题

    所以你必须两全其美。

    您可以扩展类PropertyNamingStrategy 以对单个类的反序列化和序列化具有不同的命名策略。

    在一个简单的情况下覆盖这些方法:

    而可变类和不可变类都使用 getter 进行序列化, setter 反序列化仅适用于可变类。

    对于不可变类和基于构造函数的反序列化(例如,使用 Lombok 的 @Builder 定义的 builder-pattern),您可以覆盖该方法:

    然后用@JsonNaming注解目标类

    示例

    例如,当您的扩展命名策略是:

    public class CamelToSnakeStrategy extends PropertyNamingStrategy {
    
        @Override
        public String nameForField(MapperConfig<?> config, AnnotatedField field, String defaultName) {
            return defaultName;  // no translation
        }
    
        @Override
        public String nameForGetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
            return PropertyNamingStrategy.SNAKE_CASE.translate(defaultName);
        }
    
        @Override
        public String nameForSetterMethod(MapperConfig<?> config, AnnotatedMethod method, String defaultName) {
            return PropertyNamingStrategy.LOWER_CAMEL_CASE.translate(defaultName);
        }
    
        @Override
        public String nameForConstructorParameter(MapperConfig<?> config, AnnotatedParameter ctorParam,
                String defaultName) {
            return PropertyNamingStrategy.LOWER_CAMEL_CASE.translate(defaultName);
        }
    }
    

    然后使用 @JsonNaming 策略注释您的目标 bean:

    @JsonNaming(CamelToSnakeStrategy.class) // use different cased naming: camel for in / snake for out
    @JsonInclude(JsonInclude.Include.NON_NULL)
    @JsonDeserialize(builder = Layout.LayoutBuilder.class)
    @Builder
    @Getter
    public class Layout {
       
        // property default-name is translated 
        private final String myBanner;
    }
    

    您还可以在模块中或在您的(全局)ObjectMapper 中配置此命名策略:

    objectMapper.setPropertyNamingStrategy(CamelToSnakeStrategy.class);
    

    解决Builder-Deserialization问题

    在反序列化 不可变 对象时,这意味着字段为 final 以拒绝状态修改。因此,setter 不是有效的操作,只能使用构造函数(或生成器)来创建实例。

    Deserialize Immutable Objects with Jackson

    这意味着命名策略:

    • 构造函数用于反序列化;将命名策略应用于构造函数参数
    • getters 用于序列化;对 getter 应用特定的命名策略

    龙目岛和杰克逊

    查看相关:


    另请参阅相关问题:

    【讨论】:

    • @Fllappy 不,Layout 是您的 target-bean,将使用 @JsonNaming 进行注释。命名策略是 Jackson 的基类。这需要自定义 = 扩展。
    • 谢谢。这就是我一直在寻找的。但是 .translate() 实际上对我来说不是一个有效的方法。使用 jackson-databind-2.12.4 。在上面添加了错误截图。
    • 相信因为翻译是静态类中的非静态方法。有点困惑,除非版本之间存在一些差异,否则您如何能够调用上面的翻译。
    • 为 Layout 类添加 CamelToSnakeStrategy 作为 JsonNaming 实际上没有任何效果。尝试为所有骑行返回空字符串,期望至少会破坏响应。相反,在骆驼情况下可以很好地返回响应。
    猜你喜欢
    • 2016-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-08
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多