Как получить стандартный вывод сериализации между GO и JS относительно значений по умолчанию?

Я беру часть данных и помещаю ее в protobuf как в GO, так и в JS, а затем кодирую PB на каждой платформе, и результирующие сериализованные значения различаются. Поскольку мы используем закодированное значение для подписи и хэширования, очень важно, чтобы они совпадали. Из того, что я мог бы собрать, я думаю, что разница в том, что JS включает значения по умолчанию в закодированный вывод, а GO - нет.

Обе платформы начинаются с одного и того же куска JSON. Вот GO:

listing := new(pb.Listing)
err = jsonpb.UnmarshalString(string(jsonListing), listing)
ser, err := proto.Marshal(listing)
sEnc := b64.StdEncoding.EncodeToString(ser)

значение кодирования base64 от GO:

ChR0ZXN0LXRlc3QtdGVzdC1taWxseRLEAQouUW1RMlRoQkw2emNZeEJzQ0gyZlVWM0VVaVBNM3RZbWRuUDNxN2prZTIxdUJNcBpJCiQIARIg1JRiC99XTy49u47TrmPhebH2IoWanvr9rfG2+cj8O4YSIQLJRSKlbpvhAB4nyf2yr0gTbVTXwn8uL41usco/cwtyliJHMEUCIQDqnEyTrFKKNY0FRlbn9wC4+69ozF8C3meKcLQG36nseQIgfJs1dJdFTSM2lGg7hQ68O1PVjAZHWO2XRaogo3OMeUgaKwgEIgYI4LSc/wcqA0xUQyoDQlRDKgNCQ0gqA1pFQzIDVVNEQLgIUIDC1y8ioAIKFFRFU1QgVEVTVCBURVNUIG1pbGx5IGQ6/gEKDG11cmFrYW1pLmpwZxIuUW1WeVZIOFJhbTZNZTNpaHlLZ2p6SnNNMlhaeG5QajZQS1NmbmVSRmY4WmFhRBouUW1laFFoMlNDeVZuWXpZNTduVFAzOWRrbUU3Z0t5ekpHeUhUTko0dXpDM2QyRCIuUW1YcTFSTEt0d2E3VmNSemFhN0dTWEtoVWdIYnBicUhNZWhVS2RDeVVTV1hvNyouUW1VeHlBdHYzdzgxWVFnaEFtckVHTThpbjRYU01QNkROZEVnY1RqNm12UXRjMzIuUW1TODhUcVgySzlwU1VvdnFjczNXbkdhUDFRQjdoTXNSUHdMZFVXNmR5UzRoTFIDTkVXYgAqKQoMVVNBIHNoaXBzdGVyEAEaAuoBKhMKCFN0YW5kYXJkEBkaAzUtNyAK

Вот что делает JS:

const ListingPB = getProtoContractsRoot().lookupType('Listing');
const listingPB = ListingPB.fromObject(jsonListing);
const ser = ListingPB
  .encode(listingPB)
  .finish();

Что приводит к:

ChR0ZXN0LXRlc3QtdGVzdC1taWxseRLGAQouUW1RMlRoQkw2emNZeEJzQ0gyZlVWM0VVaVBNM3RZbWRuUDNxN2prZTIxdUJNcBIAGkkKJAgBEiDUlGIL31dPLj27jtOuY+F5sfYihZqe+v2t8bb5yPw7hhIhAslFIqVum+EAHifJ/bKvSBNtVNfCfy4vjW6xyj9zC3KWIkcwRQIhAOqcTJOsUoo1jQVGVuf3ALj7r2jMXwLeZ4pwtAbfqex5AiB8mzV0l0VNIzaUaDuFDrw7U9WMBkdY7ZdFqiCjc4x5SBo6CAQQABgAIggI4LSc/wcQACoDTFRDKgNCVEMqA0JDSCoDWkVDMgNVU0Q6AEC4CEoAUIDC1y9dAAAAACKxAgoUVEVTVCBURVNUIFRFU1QgbWlsbHkSABoAIGQoADr+AQoMbXVyYWthbWkuanBnEi5RbVZ5Vkg4UmFtNk1lM2loeUtnanpKc00yWFp4blBqNlBLU2ZuZVJGZjhaYWFEGi5RbWVoUWgyU0N5Vm5Zelk1N25UUDM5ZGttRTdnS3l6Skd5SFROSjR1ekMzZDJEIi5RbVhxMVJMS3R3YTdWY1J6YWE3R1NYS2hVZ0hicGJxSE1laFVLZEN5VVNXWG83Ki5RbVV4eUF0djN3ODFZUWdoQW1yRUdNOGluNFhTTVA2RE5kRWdjVGo2bXZRdGMzMi5RbVM4OFRxWDJLOXBTVW92cWNzM1duR2FQMVFCN2hNc1JQd0xkVVc2ZHlTNGhMTQAAAABSA05FV2IGEgAYACAAKikKDFVTQSBzaGlwc3RlchABGgLqASoTCghTdGFuZGFyZBAZGgM1LTcgCkoAUgA=

... что отличается от того, что придумывает GO.

Если я перенесу обе эти строки base64 в JS, декодирую их в PB, а затем toJSON() что PB и посмотрите на разницу двух объектов, похоже, разница в том, что JS сериализует значения по умолчанию, а GO нет (JS справа).

Я попытался сериализовать JS, но результат тот же:

    const ser =
      ListingPB
        .encode(ListingPB.toObject(listingPB, { defaults: false }))
        .finish();

Итак, есть ли способ сделать вывод согласованным между двумя платформами? Необработанные входные данные в формате JSON вводятся одинаково, но результаты на выходе отличаются.

1 ответ

Ладно, это не идеальное решение, но это не очень приятное решение, пока не появится реальное решение, которое, вероятно, потребует изменения кода в библиотеке protobufjs.

Вместо того, чтобы использовать стандарт Message.encodeпозвонил бы goEncode который удалит любые перечисляемые поля, для которых установлено значение по умолчанию (т. е. значение равно 0).

function convertFields(obj, PB) {
  const converted = Object
    .keys(obj)
    .reduce((converted, field) => {
      const fieldType = PB.fields[field];

      if (fieldType) {
        const FieldPB = PB[fieldType.type];

        if (fieldType.resolvedType instanceof protobuf.Enum) {
          // If the field is an Enum and it's set to the first item (default item)
          // return the nothing so the field is not included in the resulting object.
          if (FieldPB && (obj[field] === 0)) {
            return converted;
          }
        } else if (fieldType.repeated) {
          converted[field] = obj[field]
            .map(fieldObj => (
              FieldPB ?
                convertFields(fieldObj, FieldPB) : fieldObj
            ));
        } else if (FieldPB) {
          converted[field] = convertFields(obj[field], FieldPB);
          return converted;
        }
      }

      converted[field] = obj[field];
      return converted;
    }, {});

  return converted;  
}

/*
 * Will encode a protobuf in a way that matches how GO does it.
 *
 * @param {object} message - A plain javascript object or protobuf instance.
 * @param {object} PB - The protobuf class that corresponds to the provided message.
 *
 * @returns {Uint8Array} - The encoded message.
 */
export function goEncode(message, PB) {
  let messageObj = message;

  if (message instanceof protobuf.Message) {
    messageObj = PB.toObject(message, {
      defaults: false,
      arrays: false,
      objects: false,
    });
  }

  const converted = convertFields(messageObj, PB);

  return PB.encode(converted).finish();
}
Другие вопросы по тегам