如何使用Jackson Jackson将Json映射到Proto? [英] How to map Json to Proto using Jackson Mixin?

查看:151
本文介绍了如何使用Jackson Jackson将Json映射到Proto?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我们正在一个后端应用程序中使用Protobuffers作为模型/pojo文件. 我们必须调用API,该响应返回一个作为JSON的响应.

我正在寻找将JSON文件直接映射到proto的解决方案. Java文件. 作为示例,我们在项目中有Example原型:

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 Jackson,并将映射信息保留在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:无法 查找类型[简单类型,类的(Map)密钥反序列化器 com.google.protobuf.Descriptors $ FieldDescriptor]

请帮我找到一个解决此问题的好方法.

解决方案

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 . /p>

您的情况下的示例用法如下所示:

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>

We are working in an backend application in which we use Protobuffers as model/pojo files. We have to call an API which returns a response as a JSON.

I am looking for a solution to map JSON files directly to proto. java files. As an example, we have Example proto in our project:

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

Now we need to call an API which returns response in JSON:

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

Now, I want to map the response(which is in json) directly with Proto. please let me know how to do this. Please note since Example.java is an auto generated java file I cannot make any changes in this class. Also, please note the fields of json and proto are different.

Here's what I have tried. I tried to use Jackson Mixin and keep the mapping information in the mixin class but it didnt work and throw some weird FieldDiscriptor error.

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;
}

and example usage:

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

Exception:

om.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot find a (Map) Key deserializer for type [simple type, class com.google.protobuf.Descriptors$FieldDescriptor]

Please, help me find a good solution to this issue.

解决方案

Classes generated by protoc compiler are not simple POJO. They contain many different methods and types we need to "filter out" to make Jackson work.

Fixed MixIn class

Indeed there is a simpler solution than custom deserialiser. You need to ignore Map<Descriptors.FieldDescriptor, Object> getAllFields() method and improve field names by adding underscore: _.

Example:

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();
}

Above code prints:

id: "3"
another_id: "43"
code: 34
name: "Yeyproto"
currTime {
  seconds: 1575909372
  nanos: 35000000
}

2019-12-09T16:36:12.035Z

Custom deserialiser + com.hubspot library

In that case, the simplest solution is to write set of deserialisers and serialisers for all com.google.protobuf.* types are compiled into POJO. Luckily, there is already implemented module which handles them: jackson-datatype-protobuf.

Example usage in your case could look like below:

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();
    }
}

Example code prints:

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

Maven dependencies:

<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>

这篇关于如何使用Jackson Jackson将Json映射到Proto?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆