Java: JSON-> Protobuf & back 转换

我有一个现有的系统,使用的是 基于原型药的通信协议之间的 GUI 和服务器。现在,我想添加一些持久性,但是目前,Protobuf 消息被直接转换为第三方自定义对象。

是否有方法将 原型消息转换为 Json,然后将其持久化到数据库。

注意: 我不太喜欢将二进制原型写入数据库的想法,因为有一天它可能会与新版本不向后兼容,从而破坏系统。

170402 次浏览

我们目前正在使用 Probuf-java-format将 Protobuf 消息(Message的任何子类)转换为 JSON 格式,以便通过我们的 web API 发送。

简单来说:

  JsonFormat.printToString(protoMessage)

我不太喜欢将二进制原型写入数据库的想法,因为有一天它会变得不能向后兼容新版本,从而破坏系统。

将 Protobuf 转换为 JSON 用于存储,然后在加载时返回 Protobuf,这很可能会产生兼容性问题,因为:

  • 如果执行转换的进程没有使用最新版本的 Protobuf 模式构建,那么转换将无声地删除进程不知道的任何字段。无论是存储端还是加载端都是如此。
  • 即使使用最新的模式,当存在不精确的浮点值和类似的拐角情况时,JSON <-> Protobuf 转换也可能是有损耗的。
  • 与 JSON 相比,Protobufs 实际上具有(稍微)更强的向后兼容性保证。与 JSON 类似,如果添加一个新字段,旧客户机将忽略它。与 JSON 不同,Protobufs 允许声明默认值,这可以使新客户机更容易处理在其他方面缺少该字段的旧数据。这只是一个小小的优势,但除此之外 Protobuf 和 JSON 具有相同的向后兼容性属性,因此在 JSON 存储并不会获得任何向后兼容性优势。

尽管如此,还是有很多库可以将 Protobuf 转换成 JSON,通常构建在 Protobuf 反射接口上(不要与 Java 反射接口混淆; Protobuf 反射是由 com.google.protobuf.Message接口提供的)。

正如在 对类似问题的回答中提到的,由于 V3.1.0,这是 ProtocolBuffers 支持的特性。对于 Java,包含扩展模块 Protobuf: Protobuf-java-util并使用 JsonFormat,如下所示:

JsonFormat.parser().ignoringUnknownFields().merge(json, yourObjectBuilder);
YourObject value = yourObjectBuilder.build();

JsonFormat 甚至在 Protobuf 3.0之前就已经可以使用了,这为 Ophir提供了新的答案。然而,这样做的方式有点不同。

在 Protobuf 3.0 + 中,JsonFormat 类是一个单例类,因此可以执行以下操作

String jsonString = "";
JsonFormat.parser().ignoringUnknownFields().merge(jsonString,yourObjectBuilder);

在 Protobuf 2.5 + 中,下面的代码应该可以工作

String jsonString = "";
JsonFormat jsonFormat = new JsonFormat();
jsonString = jsonFormat.printToString(yourProtobufMessage);

下面是我编写的一个 教程的链接,它使用 TypeAdapter 中的 JsonFormat 类,该类可以注册到 GsonBuilder 对象。然后可以使用 Gson 的 toJson 和 from Json 方法将 proto 数据转换为 Java 并返回。

回复 Jean。如果我们在一个文件中包含了 Protobuf 数据,并且希望将其解析为 Protobuf 消息对象,那么可以使用 merge 方法 文本格式类。请参阅下面的代码片段:

// Let your proto text data be in a file MessageDataAsProto.prototxt
// Read it into string
String protoDataAsString = FileUtils.readFileToString(new File("MessageDataAsProto.prototxt"));


// Create an object of the message builder
MyMessage.Builder myMsgBuilder = MyMessage.newBuilder();


// Use text format to parse the data into the message builder
TextFormat.merge(protoDataAsString, ExtensionRegistry.getEmptyRegistry(), myMsgBuilder);


// Build the message and return
return myMsgBuilder.build();

试试 JsonFormat.printer().print(MessageOrBuilder)看起来很适合原型3。然而,目前还不清楚如何转换实际的 protobuf消息(它是作为我在。Proto 文件)到 com.google.protbuf。消息对象。

对于 Protobuf 2.5,使用依赖项:

"com.googlecode.protobuf-java-format" % "protobuf-java-format" % "1.2"

然后使用密码:

com.googlecode.protobuf.format.JsonFormat.merge(json, builder)
com.googlecode.protobuf.format.JsonFormat.printToString(proto)

根据我的发现,没有捷径可走,但是你
用几个简单的步骤就可以实现

首先,您必须声明一个类型为‘ ProtobufJsonFormatHttpMessageConverter’的 bean

@Bean
@Primary
public ProtobufJsonFormatHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufJsonFormatHttpMessageConverter(JsonFormat.parser(), JsonFormat.printer());
}

然后您可以编写一个类似 ResponseBuilder 的 Utilityclass,因为默认情况下它可以解析请求,但是如果没有这些更改,它就不能生成 Json 响应。然后您可以编写一些方法来将响应类型转换为相关的对象类型。

public static <T> T build(Message message, Class<T> type) {
Printer printer = JsonFormat.printer();
Gson gson = new Gson();
try {
return gson.fromJson(printer.print(message), type);
} catch (JsonSyntaxException | InvalidProtocolBufferException e) {
throw new ApiException(HttpStatus.INTERNAL_SERVER_ERROR, "Response   conversion Error", e);
}
}

然后您可以从控制器类中调用这个方法作为最后一行,如-

return ResponseBuilder.build(<returned_service_object>, <Type>);

希望这将有助于您实现 json 格式的 Protobuf。

仿制药解决方案

下面是 Json 转换器的通用版本

package com.github.platform.util;


import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import com.google.protobuf.AbstractMessage.Builder;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;


/**
* Generic ProtoJsonUtil to be used to serialize and deserialize Proto to json
*
* @author Marcello.deeSales@gmail.com
*
*/
public final class ProtoJsonUtil {


/**
* Makes a Json from a given message or builder
*
* @param messageOrBuilder is the instance
* @return The string representation
* @throws IOException if any error occurs
*/
public static String toJson(MessageOrBuilder messageOrBuilder) throws IOException {
return JsonFormat.printer().print(messageOrBuilder);
}


/**
* Makes a new instance of message based on the json and the class
* @param <T> is the class type
* @param json is the json instance
* @param clazz is the class instance
* @return An instance of T based on the json values
* @throws IOException if any error occurs
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <T extends Message> T fromJson(String json, Class<T> clazz) throws IOException {
// https://stackoverflow.com/questions/27642021/calling-parsefrom-method-for-generic-protobuffer-class-in-java/33701202#33701202
Builder builder = null;
try {
// Since we are dealing with a Message type, we can call newBuilder()
builder = (Builder) clazz.getMethod("newBuilder").invoke(null);


} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
return null;
}


// The instance is placed into the builder values
JsonFormat.parser().ignoringUnknownFields().merge(json, builder);


// the instance will be from the build
return (T) builder.build();
}
}


使用方法简单如下:

消息实例

GetAllGreetings.Builder allGreetingsBuilder = GetAllGreetings.newBuilder();


allGreetingsBuilder.addGreeting(makeNewGreeting("Marcello", "Hi %s, how are you", Language.EN))
.addGreeting(makeNewGreeting("John", "Today is hot, %s, get some ice", Language.ES))
.addGreeting(makeNewGreeting("Mary", "%s, summer is here! Let's go surfing!", Language.PT));


GetAllGreetings allGreetings = allGreetingsBuilder.build();

敬 Json Generic

String json = ProtoJsonUtil.toJson(allGreetingsLoaded);
log.info("Json format: " + json);

来自 Json Generic

GetAllGreetings parsed = ProtoJsonUtil.fromJson(json, GetAllGreetings.class);
log.info("The Proto deserialized from Json " + parsed);

这是我的实用类,你可以用:

package <removed>;
import com.google.protobuf.Message;
import com.google.protobuf.MessageOrBuilder;
import com.google.protobuf.util.JsonFormat;
/**
* Author @espresso stackoverflow.
* Sample use:
*      Model.Person reqObj = ProtoUtil.toProto(reqJson, Model.Person.getDefaultInstance());
Model.Person res = personSvc.update(reqObj);
final String resJson = ProtoUtil.toJson(res);
**/
public class ProtoUtil {
public static <T extends Message> String toJson(T obj){
try{
return JsonFormat.printer().print(obj);
}catch(Exception e){
throw new RuntimeException("Error converting Proto to json", e);
}
}
public static <T extends MessageOrBuilder> T toProto(String protoJsonStr, T message){
try{
Message.Builder builder = message.getDefaultInstanceForType().toBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(protoJsonStr,builder);
T out = (T) builder.build();
return out;
}catch(Exception e){
throw new RuntimeException(("Error converting Json to proto", e);
}
}
}

返回文章页面 Google Protobuf3.7.0的最新答案:
Maven change ——将其添加到 pom.xml:

<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
<version>3.7.0</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<additionalProtoPathElements>
<additionalProtoPathElement>${project.basedir}/src/main/resources</additionalProtoPathElement>
</additionalProtoPathElements>
<protocArtifact>com.google.protobuf:protoc:3.7.0:exe:${os.detected.classifier}</protocArtifact>
</configuration>
</plugin>

这是 java 类:

public class ProtobufTrial {
public static void main(String[] args) throws Exception {
String jsonString = "";
MyProto.MyEventMsg.Builder builder = MyProto.MyEventMsg.newBuilder();
JsonFormat.parser().ignoringUnknownFields().merge(jsonString, builder);
MyProto.MyEventMsg value = builder.build();


// This is for printing the proto in string format
System.out.println(JsonFormat.printer().print(value));
}
}

我编写了一个简单的库(给你)来将协议生成的 Java 对象转换成任何 Jackson 支持的数据格式(比如 JSON、 YAML 或者任何你能想到的格式)。这允许我在转换后的输出上使用所有 Jackson 支持的序列化特性。它基于谷歌的 Probuf-java-util库。

添加依赖项并按如下方式使用库:

MyMessage myMessage = MyMessage.newBuilder().build();
JsonMapper jsonMapper = JsonMapper.builder()
.addModule(JavaProtoModule.builder().build()).build();
String serialized = jsonMapper.writeValueAsString(myMessage);