You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何JsonFormat.parser()无法正确解析FieldMask?

Protobuf FieldMask解析时蛇形命名导致合并失效的解决方案

问题描述

在旧项目中使用JsonFormat.parser()解析JSON时,传入的驼峰格式FieldMask(如fullName)会被自动转换为蛇形命名(如full_name),导致FieldMaskUtil.merge()无法按预期工作。而在Spring Boot项目中通过添加ProtobufHttpMessageConverter Bean可以正常运行。

请求示例:

{
    "user": {
        "fullName": "Abc"
    },
    "mask": "fullName"
}

解析后得到的mask值为full_name,与预期不符。

相关代码:

User byId = userRepo.getById(id);
if (byId == null) {
    throw new NotFoundException("User not found");
}
User.Builder destination = byId.toBuilder();
FieldMaskUtil.merge(updateUserRequest.getMask(), updateUserRequest.getUser(), destination);

解决方案

方案一:使用proto定义的蛇形命名传入FieldMask

Protobuf的JsonFormat默认遵循proto文件的字段命名规范(推荐蛇形命名),直接在请求中传入proto定义的字段名即可:

{
    "user": {
        "fullName": "Abc"
    },
    "mask": "full_name"
}

这种方式无需修改后端代码,解析后的FieldMask会直接匹配proto字段名,FieldMaskUtil.merge()可正常执行。

方案二:配置JsonFormat.parser保留原始字段名(不推荐)

如果无法修改前端请求格式,可以在创建JsonFormat解析器时,配置禁用字段名转换逻辑:

JsonFormat parser = JsonFormat.parser()
    .preservingProtoFieldNames()
    .ignoringUnknownFields();

// 使用该解析器解析请求体
UpdateUserRequest request = parser.merge(jsonString, UpdateUserRequest.newBuilder()).build();

注意:preservingProtoFieldNames会让解析器严格按照proto文件的字段名匹配,可能影响其他字段的解析,需谨慎使用。

方案三:手动转换FieldMask命名格式

如果需要兼容驼峰格式的输入,可以在解析后手动将蛇形命名转换为驼峰命名,再执行合并:

User byId = userRepo.getById(id);
if (byId == null) {
    throw new NotFoundException("User not found");
}
User.Builder destination = byId.toBuilder();

// 获取解析后的蛇形FieldMask,转换为驼峰格式
FieldMask originalMask = updateUserRequest.getMask();
List<String> camelCasePaths = originalMask.getPathsList().stream()
    .map(this::snakeToCamel)
    .collect(Collectors.toList());
FieldMask camelCaseMask = FieldMask.newBuilder().addAllPaths(camelCasePaths).build();

// 使用转换后的FieldMask执行合并
FieldMaskUtil.merge(camelCaseMask, updateUserRequest.getUser(), destination);

蛇形转驼峰工具方法

private String snakeToCamel(String snakeCase) {
    StringBuilder sb = new StringBuilder();
    boolean capitalizeNext = false;
    for (char c : snakeCase.toCharArray()) {
        if (c == '_') {
            capitalizeNext = true;
        } else {
            sb.append(capitalizeNext ? Character.toUpperCase(c) : c);
            capitalizeNext = false;
        }
    }
    return sb.toString();
}

内容的提问来源于stack exchange,提问作者Kshitij Dhakal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 00:05:24