web-dev-qa-db-fra.com

Java: JSON -> Protobuf et conversion arrière

J'ai un système existant, qui utilise le protocole de communication basé sur protobuf entre l'interface graphique et le serveur. J'aimerais maintenant ajouter un peu de persistance, mais pour le moment, les messages protobuf sont directement convertis en objets personnalisés tiers.

Y at-il un moyen de convertir proto messages en json , ce qui pourrait être ensuite persisté à la base de données.

N.B.: Je n'aime pas trop l'idée d'écrire protobuf binaire dans une base de données, car il pourrait un jour devenir non compatible avec les versions les plus récentes et casser le système de cette façon.

36
Denis Kulagin

Nous utilisons actuellement format Java-protobuf pour convertir nos messages Protobuf (toute sous-classe de Message) en un format JSON à envoyer via notre API Web.

Faites simplement:

  JsonFormat.printToString(protoMessage)
38
jas_raj

Comme mentionné dans réponse à une question similaire , puisque v3.1. , il s'agit d'une fonctionnalité prise en charge de ProtocolBuffers. Pour Java, incluez le module d'extension (com.google.protobuf: protobuf-Java-util et utilisez JsonFormat comme suit:

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

Je n'aime pas beaucoup l'idée d'écrire protobuf binaire dans une base de données, car il pourrait un jour devenir rétro-compatible avec les versions les plus récentes et casser le système de cette façon.

La conversion de protobuf en JSON pour le stockage, puis de nouveau en protobuf au chargement est beaucoup plus susceptible de créer des problèmes de compatibilité, car:

  • Si le processus qui effectue la conversion n'est pas construit avec la dernière version du schéma protobuf, la conversion supprime automatiquement les champs inconnus du processus. Cela est vrai à la fois pour le stockage et le chargement.
  • Même avec le schéma le plus récent disponible, la conversion JSON <-> Protobuf peut entraîner des pertes en présence de valeurs à virgule flottante imprécises et de cas angulaires similaires.
  • Les protobufs ont en fait une garantie de compatibilité ascendante (légèrement) plus forte que JSON. Comme avec JSON, si vous ajoutez un nouveau champ, les anciens clients l'ignoreront. Contrairement aux JSON, les Protobufs autorisent la déclaration d'une valeur par défaut, ce qui peut aider les nouveaux clients à gérer plus facilement les anciennes données pour lesquelles le champ manque. Il ne s'agit que d'un léger avantage, mais sinon Protobuf et JSON ont des propriétés de compatibilité ascendante équivalentes. Par conséquent, vous ne bénéficiez d'aucun avantage de compatibilité ascendante grâce au stockage dans JSON.

Cela dit, il existe de nombreuses bibliothèques pour la conversion de protobufs en JSON, généralement construites sur l’interface de réflexion Protobuf (à ne pas confondre avec l’interface de réflexion Java; la réflexion Protobuf est proposée par le com.google.protobuf.Message interface).

34
Kenton Varda

Ajoutant à la réponse de Ophir , JsonFormat est disponible même avant le lancement de protobuf 3.0. Cependant, la façon de le faire diffère un peu.

Dans Protobuf 3.0+, la classe JsonFormat est un singleton et fait donc quelque chose comme ci-dessous.

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

Dans Protobuf 2.5+, ce qui suit devrait fonctionner

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

Voici un lien vers un tutoriel que j'ai écrit et qui utilise la classe JsonFormat dans un TypeAdapter pouvant être inscrit dans un objet GsonBuilder. Vous pouvez ensuite utiliser les méthodes toJson et fromJson de Gson pour convertir les données proto en Java et inversement.

Répondre à jean . Si nous avons les données protobuf dans un fichier et que nous souhaitons les analyser dans un objet de message protobuf, utilisez la méthode de fusion TextFormat class. Voir l'extrait ci-dessous:

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

Essayez JsonFormat.printer().print(MessageOrBuilder), ça a l'air bien pour proto3. Cependant, il est difficile de savoir comment convertir le message protobuf réel (fourni sous la forme du package Java de mon choix défini dans le fichier .proto) en un fichier com.google. Objet protbuf.Message.

4
jean

Pour protobuf 2.5, utilisez la dépendance:

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

Ensuite, utilisez le code:

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

Eh bien, il n'y a pas de raccourci pour le faire selon mes conclusions, mais de toute façon vous
et y parvenir en quelques étapes simples

Vous devez d'abord déclarer un bean de type 'ProtobufJsonFormatHttpMessageConverter'

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

Ensuite, vous pouvez simplement écrire une classe Utility telle que ResponseBuilder, car elle peut analyser la demande par défaut, mais sans ces modifications, elle ne peut pas produire de réponse Json. Vous pouvez ensuite écrire quelques méthodes pour convertir les types de réponse en type d'objet associé.

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

Ensuite, vous pouvez appeler cette méthode depuis votre classe de contrôleur en dernière ligne, par exemple -

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

J'espère que cela vous aidera à implémenter protobuf au format json.

2
Bharat

Solution générique

Voici une version générique du convertisseur 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/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();
  }
}

Son utilisation est aussi simple que suit:

Instance de message

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

Pour Json Generic

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

De Json Generic

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