tanagra-protobuf/decode-entity.js

const protobuf = require('protobufjs')

const addSerializableClasses = require('tanagra-core').addSerializableClasses
const serializableClassMappings = require('tanagra-core').serializableClassMappings.get

const CustomReader = require('./custom-reader')

function deserializeMap(mapFieldName, result, serializable) {
  const map = new Map()
  result[mapFieldName].forEach(kvp => {
    // Set prototypes on kvp's key and value
    if (kvp._keySerializationKey) {
      const proto = serializable.get(kvp._keySerializationKey)
      if (proto) {
        Object.setPrototypeOf(kvp.key, proto)
      }
    }

    if (kvp._valueSerializationKey) {
      const proto = serializable.get(kvp._valueSerializationKey)
      if (proto) {
        Object.setPrototypeOf(kvp.value, proto)
      }
    }

    // Add entry to map
    map.set(kvp.key, kvp.value)
  })

  const originalKey = mapFieldName.replace('_map', '')
  result[originalKey] = map

  delete result[mapFieldName]

  return map
}

function denormaliseInstance(instance, serializable) {
  if (instance._serializationKey) {
    const proto = serializable.get(instance._serializationKey)
    if (proto) {
      Object.setPrototypeOf(instance, proto)
    }
  }

  Object.entries(instance).map(entry => ({ key: entry[0], value: entry[1] })).filter(kvp => kvp.value).forEach(kvp => {
    if (kvp.key.indexOf('_map') !== -1) {
      const map = deserializeMap(kvp.key, instance, serializable)
      Array.from(map.values()).forEach(v => denormaliseInstance(v, serializable))
    } else if (kvp.value.constructor.name === 'Array') {
      kvp.value.forEach(v => denormaliseInstance(v, serializable))
    } else if (kvp.value._serializationKey) {
      denormaliseInstance(kvp.value, serializable)
    }
  })
}

function decodeEntity(tuple, clazz) {
  const entity = new Buffer(tuple.encoded || [])
  const json = global.protobuf.Type.decode(tuple.schema)
  const message = protobuf.Type.fromJSON(tuple.type, json)
  const instance = message.decode(new CustomReader(entity))

  if (clazz) {
    addSerializableClasses(clazz)
  }

  denormaliseInstance(instance, serializableClassMappings())
  return instance
}

/**
 * Deserializes a class instance that was serialized in Google protobuffers format.
 *
 * @memberOf module:tanagra-protobuf
 * @function decodeEntity
 * @param encoded Serialized instance, with class metadata.
 * @param clazz Type parameter specifying class to deserialize to.
 *
 * @returns Object Deserialized instance of specified type.
 * @example
 * const protobuf = require('tanagra-protobuf')
 * protobuf.init()
 * const foo = protobuf.decodeEntity(tuple, SomeDecoratedClass)
 */
module.exports = decodeEntity