【问题标题】:How to map Json to Proto using Jackson Mixin?如何使用 Jackson Mixin 将 Json 映射到 Proto?
【发布时间】:2019-12-03 12:35:01
【问题描述】:

我们正在使用Protobuffers 作为模型/pojo 文件的后端应用程序。 我们必须调用API,它会以JSON 的形式返回响应。

我正在寻找将JSON 文件直接映射到原型的解决方案。 java 文件。 例如,我们的项目中有 Example proto:

message Example{
 string id                    = 1;
 string another_id            = 2;
 int32 code                   = 3;
 string name                 = 4;
}

现在我们需要调用API,它在JSON 中返回响应:

{
   "json_id":"3",
   "json_another_id":"43",
   "code":34,
   "json_name":"Yeyproto"
}

现在,我想直接用 Proto 映射响应(在 json 中)。 请让我知道如何做到这一点。请注意,由于 Example.java 是一个自动生成的 java 文件,因此我无法在此类中进行任何更改。另外,请注意 json 和 proto 的字段是不同的。

这是我尝试过的。 我尝试使用 Jackson Mixin 并将映射信息保留在 mixin 类中,但它不起作用并引发了一些奇怪的 FieldDiscriptor 错误。

public abstract class UserMixin { 
    @JsonProperty("json_id")
    String id;

    @JsonProperty("json_another_id")
    String another_id;

    @JsonProperty("code")
    int code;

    @JsonProperty("json_name")
    String name;
}

和示例用法:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.addMixIn(Example.class, ExampleMixin.class);
Position usr = objectMapper.readerFor(Example.class).readValue(json);
System.out.println(json);

例外:

om.fasterxml.jackson.databind.exc.InvalidDefinitionException: 不能 为类型 [simple type, class com.google.protobuf.Descriptors$FieldDescriptor]

请帮我找到解决这个问题的好办法。

【问题讨论】:

    标签: java json jackson protocol-buffers jackson-databind


    【解决方案1】:

    protoc 编译器生成的类并不简单POJO。它们包含许多我们需要“过滤掉”以使Jackson 工作的不同方法和类型。

    修复MixIn

    确实有比自定义反序列化器更简单的解决方案。您需要忽略Map<Descriptors.FieldDescriptor, Object> getAllFields() 方法并通过添加下划线来改进字段名称:_

    例子:

    import com.celoxity.protobuf.ExampleOuterClass.Example;
    import com.fasterxml.jackson.annotation.JsonIgnore;
    import com.fasterxml.jackson.annotation.JsonProperty;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.google.protobuf.Descriptors;
    import com.google.protobuf.Timestamp;
    
    import java.time.Instant;
    import java.util.Map;
    
    public class ProtobufApp {
        public static void main(String[] args) throws Exception {
            ObjectMapper mapper = JsonMapper.builder()
                    .enable(SerializationFeature.INDENT_OUTPUT)
                    .addMixIn(Example.class, ExampleMixin.class)
                    .addMixIn(Timestamp.class, TimestampMixin.class)
                    .build();
    
            String json = "{" +
                    "\"json_id\":\"3\"," +
                    "\"json_another_id\":\"43\"," +
                    "\"code\":34," +
                    "\"json_name\":\"Yeyproto\"," +
                    "\"currTime\":{\"seconds\":1575909372,\"nanos\":35000000}" +
                "}";
            Example deserialised = mapper.readValue(json, Example.class);
    
            System.out.println(deserialised);
            Timestamp currTime = deserialised.getCurrTime();
            System.out.println(Instant.ofEpochSecond(currTime.getSeconds(), currTime.getNanos()));
        }
    }
    
    abstract class ExampleMixin extends ProtoBufIgnoredMethods {
    
        @JsonProperty("json_id")
        String id_;
    
        @JsonProperty("json_another_id")
        String anotherId_;
    
        @JsonProperty("code")
        int code_;
    
        @JsonProperty("json_name")
        String name_;
    
        @JsonProperty("currTime")
        Timestamp currTime_;
    }
    
    abstract class TimestampMixin extends ProtoBufIgnoredMethods {
        @JsonProperty("seconds")
        String seconds_;
    
        @JsonProperty("nanos")
        String nanos_;
    }
    
    abstract class ProtoBufIgnoredMethods {
        @JsonIgnore
        public abstract Map<Descriptors.FieldDescriptor, Object> getAllFields();
    }
    

    上面的代码打印:

    id: "3"
    another_id: "43"
    code: 34
    name: "Yeyproto"
    currTime {
      seconds: 1575909372
      nanos: 35000000
    }
    
    2019-12-09T16:36:12.035Z
    

    自定义反序列化器 + com.hubspot

    在这种情况下,最简单的解决方案是编写一组反序列化器,并将所有com.google.protobuf.*类型的序列化器编译成POJO。幸运的是,已经实现了处理它们的模块:jackson-datatype-protobuf

    您的示例用法如下所示:

    import com.fasterxml.jackson.core.JsonParser;
    import com.fasterxml.jackson.databind.DeserializationContext;
    import com.fasterxml.jackson.databind.JsonDeserializer;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    import com.fasterxml.jackson.databind.json.JsonMapper;
    import com.fasterxml.jackson.databind.module.SimpleModule;
    import com.fasterxml.jackson.databind.node.ObjectNode;
    import com.hubspot.jackson.datatype.protobuf.ProtobufModule;
    
    import java.io.IOException;
    
    public class ProtobufApp {
        public static void main(String[] args) throws Exception {
            SimpleModule pojosModule = new SimpleModule();
            pojosModule.addDeserializer(Example.class, new ExampleJsonDeserializer());
    
            ObjectMapper mapper = JsonMapper.builder()
                    .enable(SerializationFeature.INDENT_OUTPUT)
                    .addModule(new ProtobufModule())
                    .addModule(pojosModule)
                    .build();
    
            String json = "{\"json_id\":\"3\",\"json_another_id\":\"43\",\"code\":34,\"json_name\":\"Yeyproto\"}";
            Example deserialised = mapper.readValue(json, Example.class);
            System.out.println(deserialised);
        }
    }
    
    class ExampleJsonDeserializer extends JsonDeserializer<Example> {
        @Override
        public Example deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
            ObjectNode root = p.readValueAsTree();
            return Example.newBuilder()
                    .setId(root.get("json_id").asText())
                    .setAnotherId(root.get("json_another_id").asText())
                    .setName(root.get("json_name").asText())
                    .setCode(root.get("json_id").asInt())
                    .build();
        }
    }
    

    示例代码打印:

    id: "3"
    another_id: "43"
    code: 3
    name: "Yeyproto"
    

    Maven 依赖:

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.10.0</version>
    </dependency>
    <dependency>
        <groupId>com.google.protobuf</groupId>
        <artifactId>protobuf-java</artifactId>
        <version>3.11.0</version>
    </dependency>
    <dependency>
        <groupId>com.hubspot.jackson</groupId>
        <artifactId>jackson-datatype-protobuf</artifactId>
        <version>0.9.11-jackson2.9</version>
    </dependency>
    

    【讨论】:

    • 您好,感谢您的回复。但是您基本上调用 setter 和 getter 来创建 Example.java Example.newBuilder() .setId(root.get("json_id").asText()) .setAnotherId(root.get("json_another_id").asText()) 。 setName(root.get("json_name").asText()) .setCode(root.get("json_id").asInt()) .build() 我正在寻找我们可以在外部进行映射的解决方案。而且我们不必显式调用 getter 和 setter
    • @AvinashKharche,“在外部进行映射”是什么意思?在您的示例中,您使用 MixIn 类。你能解释一下最好的解决方案是什么样的吗?当然,您可以在转换为Example 之前替换名称,然后只需ProtobufModule 模块即可。
    • 1. “在外部具有映射” - 我的意思是其他一些类或文件应该在 json 和 proto 之间具有映射定义。像这样的 json_id -> id 在我的例子中,我使用了一个抽象的 mixin 类,使用它的好处是,如果我们收到新的映射/属性,那么我们不需要更改任何代码,而只需要更改映射文件 2 .如何更改json的键名正是问题所在。如果键匹配很容易将 json 转换为 proto,也可以使用 JsonFormatter
    • @AvinashKharche,使用MixIn 功能是正确的方法。它只是需要一些改进。看看更新的答案。
    • 谢谢@MichalZiober,这适用于 Mixin。我相信 Mixin 是一个很好的解决方案。你怎么看?
    【解决方案2】:

    这是另一个使用 protobuf-java-util 库将 Json 反序列化为 Proto 的示例:

            File file = new File("src/test/resources/ACDSResponse.json");
            Reader bufferedReader = new BufferedReader(new FileReader(file));
            Acds.GetLocalizedAttributesBatchResponse.Builder acdsResponseBuilder = Acds.GetLocalizedAttributesBatchResponse.newBuilder();
            JsonFormat.parser().ignoringUnknownFields().merge(bufferedReader, acdsResponseBuilder);``
    

    【讨论】:

      猜你喜欢
      • 2023-03-30
      • 1970-01-01
      • 2019-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多