Java: JSON - ›Протобуф и обратное преобразование

У меня есть существующая система, которая использует протокол связи на основе protobuf между графическим интерфейсом пользователя и сервером. Теперь я хотел бы добавить некоторую настойчивость, но на данный момент сообщения protobuf напрямую конвертируются в сторонние пользовательские объекты.

Есть ли способ преобразовать сообщения proto в json, которые затем можно было бы сохранить в базе данных.

N.B .: Мне не очень нравится идея записи двоичного protobuf в базу данных, потому что однажды он может стать несовместимым с более новыми версиями и таким образом сломать систему.


person Denis Kulagin    schedule 16.02.2015    source источник
comment
Что именно вы имеете в виду, потому что однажды он может стать несовместимым с более новыми версиями? И что заставляет вас думать, что JSON с большей вероятностью останется обратно совместимым? Вы говорите о более новых версиях вашей прототипной схемы или о новых версиях буферов протокола в целом? Мой опыт показывает, что хранение буферов протокола абсолютно нормально ...   -  person Jon Skeet    schedule 16.02.2015
comment
Обратите внимание, что Protocol Buffers 3 (в настоящее время находится в стадии бета-тестирования) будет напрямую поддерживать JSON.   -  person Josh Kelley    schedule 21.03.2016
comment
См. Также stackoverflow.com/questions/2544580/   -  person Raedwald    schedule 12.12.2017


Ответы (10)


Как упоминалось в ответе на аналогичный вопрос, поскольку v3.1.0 это поддерживаемая функция ProtocolBuffers. Для Java включите модуль расширения com.google.protobuf: protobuf -java-util и используйте JsonFormat вот так:

JsonFormat.parser().ignoringUnknownFields().merge(json, yourObjectBuilder);
YourObject value = yourObjectBuilder.build();
person Ophir Radnitz    schedule 05.03.2017

В настоящее время мы используем protobuf-java-format для преобразования наших сообщений Protobuf (любой подкласс Message) в формат JSON для отправки через наш веб-API.

Просто сделайте:

  JsonFormat.printToString(protoMessage)
person jas_raj    schedule 16.02.2015
comment
Это очень полезно. Также было бы полезно, если бы вы могли поделиться API для преобразования дампа JSON в буфер протокола. - person vanguard69; 07.09.2016
comment
Эта библиотека кажется мертвой, поскольку она находится в архивах Google Code, не экспортирована в GitHub, и важные проблемы все еще открыты. - person Louis CAD; 31.01.2017
comment
@LouisCAD да, но я думаю, что теперь он находится в developers.google.com/protocol-buffers/docs/reference/java/com/ - person helt; 01.02.2017
comment
Код находится здесь (начиная с версии 3.4.0) com.google.protobuf.util.JsonFormat.java - person Mr. Polywhirl; 02.10.2017
comment
Проект (обеспечивающий поддержку старых версий protobuf) перенесен сюда github.com/bivas/protobuf- java-формат - person nivs; 17.10.2017
comment
Похоже, в более новых версиях это значение изменилось на JsonFormat.printer().print(MessageOrBuilder). - person vdimitrov; 11.11.2017
comment
Похоже, самый последний из них находится здесь: mvnrepository.com/artifact/com .google.protobuf / с документами по адресу: javadoc.io/doc/com.google.protobuf/protobuf-java-util/latest/ - person matttm; 24.01.2020

Мне не очень нравится идея записи двоичного protobuf в базу данных, потому что однажды он может стать несовместимым с более новыми версиями и таким образом сломать систему.

Преобразование protobuf в JSON для хранения, а затем обратно в protobuf при загрузке, гораздо более может вызвать проблемы совместимости, потому что:

  • Если процесс, выполняющий преобразование, не построен с использованием последней версии схемы protobuf, то преобразование автоматически удалит все поля, о которых процесс не знает. Это верно как для хранения, так и для загрузки.
  • Даже с самой последней доступной схемой преобразование JSON ‹-> Protobuf может быть с потерями при наличии неточных значений с плавающей запятой и подобных угловых случаях.
  • Protobufs на самом деле имеет (немного) более сильные гарантии обратной совместимости, чем JSON. Как и в случае с JSON, если вы добавите новое поле, старые клиенты его проигнорируют. В отличие от JSON, Protobufs позволяет объявлять значение по умолчанию, что может несколько упростить новым клиентам работу со старыми данными, в которых в противном случае отсутствует поле. Это лишь небольшое преимущество, но в остальном Protobuf и JSON имеют эквивалентные свойства обратной совместимости, поэтому вы не получаете никаких преимуществ обратной совместимости от хранения в JSON.

С учетом всего сказанного существует множество библиотек для преобразования protobuf в JSON, обычно построенных на интерфейсе отражения Protobuf (не путать с интерфейсом отражения Java; отражение Protobuf предлагается интерфейсом com.google.protobuf.Message).

person Kenton Varda    schedule 17.02.2015
comment
Как насчет сохранения значения return из SerializeToString() в столбец базы данных ТЕКСТ? - person sivabudh; 13.06.2018
comment
SerializeToString (), несмотря на свое название, не производит текст, он производит данные в двоичной кодировке. Поэтому вам нужно сохранить его в столбце базы данных, который правильно набран для произвольных байтов. - person Kenton Varda; 14.06.2018
comment
@KentonVarda, что бы вы посоветовали для удобного для бизнес-аналитики формата хранения БД с возможностью запросов, в котором исходная полезная нагрузка приобретается в виде сообщения protobuf? Простой подход выглядит так: сообщение protobuf- ›Json-› MongoDB. Есть ли более изощренный путь, может быть, с флетбуфером? Спасибо - person Alexander.Furer; 25.12.2018

Добавляя к ответу Ophir, JsonFormat доступен даже до protobuf 3.0. Однако способ сделать это немного отличается.

В 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);

Вот ссылка на руководство Я написал, что использует класс JsonFormat в TypeAdapter, который может быть зарегистрирован в объекте GsonBuilder. Затем вы можете использовать методы Gson toJson и fromJson для преобразования прото-данных в Java и обратно.

Ответ на jean. Если у нас есть данные protobuf в файле и мы хотим проанализировать их в объект сообщения protobuf, используйте метод слияния TextFormat. См. Фрагмент ниже:

// 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();
person Moses    schedule 20.06.2017
comment
И этот для PROTO to JSON в версии 3.0+: JsonFormat.printer().print(MessageOrBuilder) - person Frederic Leitenberger; 23.08.2017

Универсальное решение

Вот общая версия конвертера 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 [email protected]
 *
 */
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);
person Marcello de Sales    schedule 26.07.2019
comment
allGreetingsLoaded - что означает этот объект? - person Arth Tilva; 05.07.2021
comment
@ArthTilva, это просто ссылка на класс Generics GetAllGreetings.class. - person Marcello de Sales; 06.07.2021

Попробуйте JsonFormat.printer().print(MessageOrBuilder), для proto3 выглядит неплохо. Тем не менее, неясно, как преобразовать фактическое сообщение protobuf (которое предоставляется как выбранный мной пакет java, определенный в файле .proto) в объект com.google.protbuf.Message.

person jean    schedule 25.08.2017

Для 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)
person Henry    schedule 10.10.2017

Что ж, нет ярлыка, чтобы сделать это, согласно моим выводам, но каким-то образом вы можете достичь этого за несколько простых шагов.

Сначала вам нужно объявить bean-компонент типа ProtobufJsonFormatHttpMessageConverter.

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

Затем вы можете просто написать класс Utility, например ResponseBuilder, потому что он может анализировать запрос по умолчанию, но без этих изменений он не может создавать ответ 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>);

Надеюсь, это поможет вам реализовать protobuf в формате json.

person Bharat    schedule 25.01.2019
comment
есть ли рабочий пример проекта github с использованием кода, подобного приведенному выше? - person Espresso; 19.02.2020
comment
Вызвано: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: для класса com.google.protobuf.UnknownFieldSet $ ​​Parser не найден сериализатор и не обнаружены свойства для создания BeanSerializer (чтобы избежать исключения, отключите SerializationFeature.FAIL_ON_EMPTY_BEANS) (через цепочку ссылок) : pn.api.protobuf.Proto $ SearchResponse [unknownFields] - ›com.google.protobuf.UnknownFieldSet [parserForType]) - person Espresso; 19.02.2020

Вот мой служебный класс, вы можете использовать:

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);
        }
    }
}
person Espresso    schedule 15.10.2020

Последний ответ с google protobuf 3.7.0:
Изменения Maven - добавьте это в свой 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));
  }
}
person voidMainReturn    schedule 18.05.2021