如何使用Jackson Jackson将Json映射到Proto? [英] How to map Json to Proto using Jackson Mixin?
问题描述
我们正在一个后端应用程序中使用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屋!