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

io.prismic.PrismicJsonProtocol.scala Maven / Gradle / Ivy

The newest version!
package io.prismic

import io.prismic.fragments._, Image.View, StructuredText.Block, StructuredText.Span._

import org.joda.time._
import org.joda.time.format.ISODateTimeFormat
import spray.json._

import PrismicJson._

import scala.util.Try

object PrismicJsonProtocol extends DefaultJsonProtocol with NullOptions {

  // Fragments

  implicit object WeblinkFormat extends RootJsonFormat[WebLink] {
    override def read(json: JsValue): WebLink = WebLink(
      (json \ "url").convertTo[String],
      (json \ "target").toOpt[String]
    )
    override def write(obj: WebLink): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object FileLinkFormat extends RootJsonFormat[FileLink] {
    override def read(json: JsValue): FileLink = FileLink(
      (json \ "file" \ "url").convertTo[String],
      (json \ "file" \ "kind").convertTo[String],
      (json \ "file" \ "size").convertTo[String].toLong,
      (json \ "file" \ "name").convertTo[String],
      (json \ "file" \ "target").toOpt[String]
    )
    override def write(obj: FileLink): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object ImageLinkFormat extends RootJsonFormat[ImageLink] {
    override def read(json: JsValue): ImageLink = ImageLink(
      (json \ "image" \ "url").convertTo[String],
      (json \ "image" \ "kind").convertTo[String],
      (json \ "image" \ "size").convertTo[String].toLong,
      (json \ "image" \ "name").convertTo[String],
      (json \ "image" \ "target").toOpt[String]
    )

    override def write(obj: ImageLink): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object DocumentLinkFormat extends RootJsonFormat[DocumentLink] {
    override def read(json: JsValue): DocumentLink = {
      val typ = (json \ "document" \ "type").convertTo[String]
      val fragments: Map[String, Fragment] = json \ "document" \ "data" \ typ match {
        case js:JsObject => DocumentFormat.parseFragments(js, typ)
        case _ => Map.empty
      }

      DocumentLink(
        (json \ "document" \ "id").convertTo[String],
        (json \ "document" \ "uid").toOpt[String],
        typ,
        (json \ "document" \ "tags").toOpt[Seq[String]].getOrElse(Nil),
        (json \ "document" \ "slug").convertTo[String],
        (json \ "document" \ "lang").convertTo[String],
        fragments,
        (json \ "isBroken").toOpt[Boolean].getOrElse(false),
        (json \ "target").toOpt[String]
      )
    }
    override def write(obj: DocumentLink): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object LinkFormat extends RootJsonFormat[Link] {
    override def read(json: JsValue): Link = (json \ "type").convertTo[String] match {
      case "Link.web" => (json \ "value").convertTo[WebLink]
      case "Link.document" => (json \ "value").convertTo[DocumentLink]
      case "Link.file" => (json \ "value").convertTo[FileLink]
      case "Link.image" => (json \ "value").convertTo[ImageLink]
      case t => throw new DeserializationException(s"Unkown link type $t")
    }
    override def write(obj: Link): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object AlternateLanguageFormat extends RootJsonFormat[AlternateLanguage] {
    override def read(json: JsValue): AlternateLanguage = AlternateLanguage(
      (json \ "id").convertTo[String],
      (json \ "uid").toOpt[String],
      (json \ "type").convertTo[String],
      (json \ "lang").convertTo[String]
    )

    override def write(obj: AlternateLanguage): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object DateFormat extends RootJsonFormat[Date] {
    override def read(json: JsValue): Date =
      Try(Date(LocalDate.parse(json.convertTo[String], format.DateTimeFormat.forPattern("yyyy-MM-dd")))).getOrElse {
        throw new DeserializationException("date parsing error ")
    }
    override def write(obj: Date): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object TextFormat extends RootJsonFormat[Text] {
    override def read(json: JsValue): Text = Text(json.convertTo[String])
    override def write(obj: Text): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object TimestampFormat extends RootJsonFormat[Timestamp] {
    override def read(json: JsValue): Timestamp = {
      val d = json.convertTo[String]
        val isoFormat = org.joda.time.format.DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ")
        Try(Timestamp(DateTime.parse(d, isoFormat).withZone(DateTimeZone.UTC)))
          .getOrElse(throw new DeserializationException(s"Invalid timestamp value $d"))
    }
    override def write(obj: Timestamp): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object NumberFormat extends RootJsonFormat[Number] {
    override def read(json: JsValue): Number = Number(json.convertTo[Double])
    override def write(obj: Number): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object ColorFormat extends RootJsonFormat[Color] {
    override def read(json: JsValue): Color = json match {
      case JsString(hex) =>
        if (Color.isValidColorValue(hex)) {
          Color(hex)
        } else {
          throw new DeserializationException(s"Invalid color value $hex")
        }
      case _ => throw new DeserializationException(s"Expected String for Color, got " + json)
    }
    override def write(obj: Color): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object EmbedFormat extends RootJsonFormat[Embed] {
    override def read(json: JsValue): Embed = Embed(
      (json \ "oembed" \ "type").convertTo[String],
      (json \ "oembed" \ "provider_name").toOpt[String],
      (json \ "oembed" \ "embed_url").convertTo[String],
      (json \ "oembed" \ "width").toOpt[Int],
      (json \ "oembed" \ "height").toOpt[Int],
      (json \ "oembed" \ "html").toOpt[String],
      json \ "oembed"
    )

    override def write(obj: Embed): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object GeoPointFormat extends RootJsonFormat[GeoPoint] {
    override def read(json: JsValue): GeoPoint = json.asJsObject.getFields("latitude", "longitude") match {
      case Seq(JsNumber(latitude), JsNumber(longitude)) => GeoPoint(latitude.toDouble, longitude.toDouble)
      case _ => throw new DeserializationException("Expected longitude and latitude as numbers")
    }
    override def write(obj: GeoPoint): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object SpanFormat extends RootJsonFormat[StructuredText.Span] {
    override def read(json: JsValue): StructuredText.Span = json.asJsObject.getFields("type", "start", "end", "data") match {
      case Seq(JsString("strong"), JsNumber(start), JsNumber(end)) => StructuredText.Span.Strong(start.toInt, end.toInt)
      case Seq(JsString("em"), JsNumber(start), JsNumber(end)) => StructuredText.Span.Em(start.toInt, end.toInt)
      case Seq(JsString("label"), JsNumber(start), JsNumber(end), JsString(label)) => StructuredText.Span.Label(start.toInt, end.toInt, label)
      case Seq(JsString("hyperlink"), JsNumber(start), JsNumber(end), data) => data.asJsObject.getFields("type", "value") match {
        case Seq(JsString("Link.web"), link) => Hyperlink(start.toInt, end.toInt, link.convertTo[WebLink])
        case Seq(JsString("Link.document"), link) => Hyperlink(start.toInt, end.toInt, link.convertTo[DocumentLink])
        case Seq(JsString("Link.file"), link) => Hyperlink(start.toInt, end.toInt, link.convertTo[FileLink])
        case Seq(JsString("Link.image"), link) => Hyperlink(start.toInt, end.toInt, link.convertTo[ImageLink])
      }
      case Seq(JsString("label"), JsNumber(start), JsNumber(end), data) => StructuredText.Span.Label(start.toInt, end.toInt, (data \ "label").convertTo[String])
    }
    override def write(obj: StructuredText.Span): JsValue = throw new SerializationException("Not implemented")
  }

  def headingFormat(level: Int) = new RootJsonFormat[StructuredText.Block.Heading] {
    override def read(json: JsValue) = StructuredText.Block.Heading(
      (json \ "text").convertTo[String],
      (json \ "spans").toOpt[Seq[StructuredText.Span]].getOrElse(Nil),
      level,
      (json \ "label").toOpt[String],
      (json \ "direction").toOpt[String]
    )
    override def write(obj: StructuredText.Block.Heading): JsValue = throw new SerializationException("Not implemented")
  }

  def listItemFormat(ordered: Boolean) = new RootJsonFormat[StructuredText.Block.ListItem] {
    override def read(json: JsValue) = StructuredText.Block.ListItem(
      (json \ "text").convertTo[String],
      (json \ "spans").toOpt[Seq[StructuredText.Span]].getOrElse(Nil),
      ordered,
      (json \ "label").toOpt[String],
      (json \ "direction").toOpt[String]
    )
    override def write(obj: StructuredText.Block.ListItem): JsValue = throw new SerializationException("Not implemented")
  }

  implicit val paragraphFormat = jsonFormat4(StructuredText.Block.Paragraph)

  implicit val preformattedFormat = jsonFormat4(StructuredText.Block.Preformatted)

  implicit object ImageViewFormat extends RootJsonFormat[Image.View] {
    override def write(obj: View): JsValue = throw new SerializationException("Not implemented")

    override def read(json: JsValue): Image.View = Image.View(
      (json \ "url").convertTo[String],
      (json \ "dimensions" \ "width").convertTo[Int],
      (json \ "dimensions" \ "height").convertTo[Int],
      (json \ "alt").toOpt[String]
    )
  }

  implicit object ImageBlockFormat extends RootJsonFormat[StructuredText.Block.Image] {
    override def write(obj: Block.Image): JsValue = throw new SerializationException("Not implemented")

    override def read(json: JsValue): Block.Image =
      Block.Image(
        json.convertTo[Image.View],
        (json \ "linkTo").toOpt[Link],
        (json \ "label").toOpt[String],
        (json \ "direction").toOpt[String]
      )
  }

  implicit val imageFormat = jsonFormat2(Image.apply)

  implicit object StructuredTextEmbedFormat extends RootJsonFormat[StructuredText.Block.Embed] {
    override def read(json: JsValue) = StructuredText.Block.Embed(
      json.convertTo[Embed],
      (json \ "label").toOpt[String],
      (json \ "direction").toOpt[String]
    )
    override def write(obj: StructuredText.Block.Embed): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object BlockFormat extends RootJsonFormat[StructuredText.Block] {
    override def read(json: JsValue): StructuredText.Block =
      json \ "type" match {
        case JsString("heading1") => json.convertTo[StructuredText.Block.Heading](headingFormat(1))
        case JsString("heading2") => json.convertTo[StructuredText.Block.Heading](headingFormat(2))
        case JsString("heading3") => json.convertTo[StructuredText.Block.Heading](headingFormat(3))
        case JsString("heading4") => json.convertTo[StructuredText.Block.Heading](headingFormat(4))
        case JsString("heading5") => json.convertTo[StructuredText.Block.Heading](headingFormat(5))
        case JsString("heading6") => json.convertTo[StructuredText.Block.Heading](headingFormat(6))
        case JsString("paragraph") => json.convertTo[StructuredText.Block.Paragraph]
        case JsString("preformatted") => json.convertTo[StructuredText.Block.Preformatted]
        case JsString("list-item") => json.convertTo[StructuredText.Block.ListItem](listItemFormat(ordered = false))
        case JsString("o-list-item") => json.convertTo[StructuredText.Block.ListItem](listItemFormat(ordered = true))
        case JsString("image") =>  json.convertTo[StructuredText.Block.Image]
        case JsString("embed") => json.convertTo[StructuredText.Block.Embed]
        case other => throw new DeserializationException(s"Unsupported block $json")
      }
    override def write(obj: StructuredText.Block): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object StructuredTextFormat extends RootJsonFormat[StructuredText] {
    override def read(json: JsValue): StructuredText = json match {
      case JsArray(elements) => StructuredText(elements.map(_.convertTo[StructuredText.Block]))
      case _ => throw new DeserializationException("Expected JsArray")
    }
    override def write(obj: StructuredText): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object FragmentFormat extends RootJsonFormat[Fragment] {
    override def read(json: JsValue): Fragment = json.asJsObject.getFields("type", "value") match {
        case Seq(JsString("Image"), value) => value.convertTo[Image]
        case Seq(JsString("Number"), value) => value.convertTo[Number]
        case Seq(JsString("Date"), value) => value.convertTo[Date]
        case Seq(JsString("Timestamp"), value) => value.convertTo[Timestamp]
        case Seq(JsString("GeoPoint"), value) => value.convertTo[GeoPoint]
        case Seq(JsString("Text"), value) => value.convertTo[Text]
        case Seq(JsString("Select"), value) => value.convertTo[Text]
        case Seq(JsString("Embed"), value) => value.convertTo[Embed]
        case Seq(JsString("Link.web"), value) => value.convertTo[WebLink]
        case Seq(JsString("Link.document"), value) => value.convertTo[DocumentLink]
        case Seq(JsString("Link.file"), value) => value.convertTo[FileLink]
        case Seq(JsString("Link.image"), value) => value.convertTo[ImageLink]
        case Seq(JsString("StructuredText"), value) => value.convertTo[StructuredText]
        case Seq(JsString("Group"), value) => value.convertTo[Group](GroupFormat)
        case Seq(JsString("SliceZone"), value) => value.convertTo[SliceZone](SliceZoneFormat)
        case Seq(JsString("Color"), value) => value.convertTo[Color]
        case Seq(JsString("Separator")) => Separator
        case Seq(JsString(t), _) => throw new DeserializationException(s"Unkown fragment type: $t")
        case _ => throw new DeserializationException("Expected JsObject with type and value, got " + json)
      }

    override def write(obj: Fragment): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object GroupFormat extends RootJsonFormat[Group] {
    override def read(json: JsValue): Group =
      Group(json.convertTo[Seq[Map[String, Fragment]]].map(Group.Doc))

    override def write(obj: Group): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object SliceFormat extends RootJsonFormat[Slice] {
    override def read(jsValue: JsValue): Slice = {
      val json = jsValue.asJsObject
      json.getFields("slice_type", "value", "non-repeat", "repeat") match {
        case Seq(JsString(sliceType), data: JsObject) =>
          val sliceLabel = (json \ "slice_label").toOpt[String]
          val fragment = data.convertTo[Fragment]
          SimpleSlice(sliceType, sliceLabel, fragment)

        case Seq(JsString(sliceType), nonRepeat: JsObject, repeat: JsArray) =>
          val sliceLabel = (json \ "slice_label").toOpt[String]
          val nr = JsArray(nonRepeat).convertTo[Group].docs.headOption.getOrElse(Group.Doc(Map()))
          val r = repeat.convertTo[Group]
          CompositeSlice(sliceType, sliceLabel, nr, r)

        case _ => throw new DeserializationException("Expected slice_type and value")
      }
    }

    override def write(obj: Slice): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object SliceZoneFormat extends RootJsonFormat[SliceZone] {
    override def read(json: JsValue) = SliceZone(json match {
      case JsArray(elements) => elements.collect {
        case jsElt if jsElt.toOpt[Slice].isDefined => jsElt.convertTo[Slice]
      }
      case _ => throw new DeserializationException("Expected JsArray")
    })

    override def write(obj: SliceZone): JsValue = throw new SerializationException("Not implemented")
  }

  implicit object DocumentFormat extends RootJsonFormat[Document] {

    override def read(jsValue: JsValue): Document = {
      val json = jsValue.asJsObject
      json.getFields("id", "href", "type", "lang", "data") match {
        case Seq(JsString(id), JsString(href), JsString(typ), JsString(lang), data: JsObject) =>
          val uid: Option[String] = (json \ "uid").toOpt[String]
          val tags: Seq[String] = (json \ "tags").toOpt[Seq[String]].getOrElse(Nil)
          val slugs: Seq[String] = (json \ "slugs").toOpt[Seq[String]].map(decode).getOrElse(Nil)
          val firstPublicationDate: Option[DateTime] = (json \ "first_publication_date").toOpt[String].map(new DateTime(_).withZone(DateTimeZone.UTC))
          val lastPublicationDate: Option[DateTime] = (json \ "last_publication_date").toOpt[String].map(new DateTime(_).withZone(DateTimeZone.UTC))
          val alternateLanguages: Seq[AlternateLanguage] = (json \ "alternate_languages").toOpt[Seq[AlternateLanguage]].getOrElse(Nil)
          val fragments: JsObject = (data \ typ).asJsObject
          Document(id, uid, typ, href, tags, slugs, firstPublicationDate, lastPublicationDate, lang, alternateLanguages, parseFragments(fragments, typ))
        case _ => throw new DeserializationException("Expected id, href, type lang and data")
      }
    }

    override def write(obj: Document): JsValue = throw new SerializationException("Not implemented")

    def parseFragments(json: JsObject, typ: String): Map[String, Fragment] = {
      val fields = json.fields.map {
        case (key, jsobj: JsObject) => jsobj.toOpt[Fragment].toList.map(fragment => (s"$typ.$key", fragment))
        case (key, jsons: JsArray) =>
          jsons.toOpt[StructuredText] match {
            case Some(structuredText) =>
              Seq(s"$typ.$key" -> structuredText)

            case None =>
              jsons.elements.zipWithIndex.collect {
                case (json: JsObject, i) => json.toOpt[Fragment].toList.map(fragment => (s"$typ.$key[$i]", fragment))
                case (jsval, i) => Nil
              }.flatten
          }

        case (key, jsval) => Nil
      }.flatten.toSeq
      collection.immutable.ListMap(fields:_*)
    }

    private def decode(slugs: Seq[String]) = slugs.map(java.net.URLDecoder.decode(_, "UTF-8"))

  }

  implicit val responseFormat = jsonFormat(Response,
    "results", "page", "results_per_page", "results_size",
    "total_results_size", "total_pages", "next_page", "prev_page"
  )

  // API Related classes

  implicit object DateTimeFormat extends RootJsonFormat[DateTime] {

    val formatter = ISODateTimeFormat.basicDateTimeNoMillis

    def write(obj: DateTime): JsValue = {
      JsString(formatter.print(obj))
    }

    def read(json: JsValue): DateTime = json match {
      case JsString(s) => try {
        formatter.parseDateTime(s)
      }
      catch {
        case t: Throwable => error(s)
      }
      case _ =>
        error(json.toString())
    }

    def error(v: Any): DateTime = {
      val example = formatter.print(0)
      deserializationError(f"'$v' is not a valid date value. Dates must be in compact ISO-8601 format, e.g. '$example'")
    }
  }

  implicit val fieldFormat = jsonFormat3(Field)

  implicit val formFormat = jsonFormat6(Form)

  implicit object RefFormat extends RootJsonFormat[Ref] {
    override def read(json: JsValue): Ref = Ref(
      (json \ "id").convertTo[String],
      (json \ "ref").convertTo[String],
      (json \ "label").convertTo[String],
      (json \ "isMasterRef").toOpt[Boolean].getOrElse(false),
      (json \ "scheduledAt").toOpt[DateTime]
    )
    override def write(obj: Ref): JsValue = throw new SerializationException("Not implemented")
  }

  implicit val variationFormat = jsonFormat3(Variation)

  implicit val experimentFormat = jsonFormat4(Experiment)

  implicit val experimentsFormat = jsonFormat(Experiments, "draft", "running")

  implicit object ApiDataFormat extends RootJsonFormat[ApiData] {
    override def read(json: JsValue): ApiData = ApiData(
      (json \ "refs").convertTo[Seq[Ref]],
      (json \ "bookmarks").toOpt[Map[String, String]].getOrElse(Map.empty),
      (json \ "types").toOpt[Map[String, String]].getOrElse(Map.empty),
      (json \ "tags").toOpt[Seq[String]].getOrElse(Nil),
      (json \ "forms").toOpt[Map[String, Form]].getOrElse(Map.empty),
      (
        (json \ "oauth_initiate").convertTo[String],
        (json \ "oauth_token").convertTo[String]
        ),
      (json \ "experiments").toOpt[Experiments].getOrElse(Experiments(Nil, Nil))
    )
    override def write(obj: ApiData): JsValue = throw new SerializationException("Not implemented")
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy