All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalapb.json4s.AnyFormat.scala Maven / Gradle / Ivy

The newest version!
package scalapb.json4s

import com.google.protobuf.any.{Any => PBAny}
import org.json4s.JsonAST.{JNothing, JObject, JString, JValue}
import org.json4s.MonadicJValue._

import scala.language.existentials

object AnyFormat {
  // Messages that have special representation are parsed/serialized from a `value` field of the
  // any.
  private val SpecialValues: Set[scalapb.GeneratedMessageCompanion[_]] = (
    com.google.protobuf.struct.StructProto.messagesCompanions ++
      com.google.protobuf.wrappers.WrappersProto.messagesCompanions ++
      Seq(
        com.google.protobuf.any.Any,
        com.google.protobuf.duration.Duration,
        com.google.protobuf.timestamp.Timestamp,
        com.google.protobuf.field_mask.FieldMask
      )
  ).toSet

  val anyWriter: (Printer, PBAny) => JValue = { case (printer, any) =>
    // Find the companion so it can be used to JSON-serialize the message. Perhaps this can be circumvented by
    // including the original GeneratedMessage with the Any (at least in memory).
    val cmp = printer.typeRegistry
      .findType(any.typeUrl)
      .getOrElse(
        throw new IllegalStateException(
          s"Unknown type ${any.typeUrl} in Any.  Add a TypeRegistry that supports this type to the Printer."
        )
      )

    // Unpack the message...
    val message = any.unpack(cmp)

    // ... and add the @type marker to the resulting JSON
    if (SpecialValues.contains(cmp))
      JObject(
        "@type" -> JString(any.typeUrl),
        "value" -> printer.toJson(message)
      )
    else
      printer.toJson(message) match {
        case JObject(fields) =>
          JObject(("@type" -> JString(any.typeUrl)) +: fields)
        case value =>
          // Safety net, this shouldn't happen
          throw new IllegalStateException(
            s"Message of type ${any.typeUrl} emitted non-object JSON: $value"
          )
      }
  }

  val anyParser: (Parser, JValue) => PBAny = {
    case (parser, obj @ JObject(fields)) =>
      obj \ "@type" match {
        case JString(typeUrl) =>
          val cmp = parser.typeRegistry
            .findType(typeUrl)
            .getOrElse(
              throw new JsonFormatException(
                s"Unknown type ${typeUrl} in Any.  Add a TypeRegistry that supports this type to the Parser."
              )
            )
          val input = if (SpecialValues.contains(cmp)) obj \ "value" else obj
          val message = parser.fromJson(input, true)(cmp)
          PBAny(typeUrl = typeUrl, value = message.toByteString)

        case JNothing =>
          throw new JsonFormatException(s"Missing type url when parsing $obj")

        case unknown =>
          throw new JsonFormatException(
            s"Expected string @type field, got $unknown"
          )
      }

    case (_, unknown) =>
      throw new JsonFormatException(s"Expected an object, got $unknown")
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy