Java: JSON → Protobuf & back conversion

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

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

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

Ответы

Ответ 1

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

Просто выполните:

  JsonFormat.printToString(protoMessage)

Ответ 3

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

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

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

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

Ответ 4

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

В Protobuf 3. 0+ класс JsonFormat является одноэлементным и поэтому выполняет что-то вроде следующего

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

В Protobuf 2. 5+ ниже должно работать

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

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

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

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

Ответ 5

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

Ответ 6

Для 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)

Ответ 7

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

Сначала вы должны объявить компонент типа "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.

Ответ 8

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

Вот общая версия конвертера Json

package com.intuit.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/info/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 go surfing!", Language.PT));

GetAllGreetings allGreetings = allGreetingsBuilder.build();

Джсону Дженерику

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

От Джсона Дженерика

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