Skip to content

Commit

Permalink
Clean up derivation of Play-JSON's Reads and Writes
Browse files Browse the repository at this point in the history
  • Loading branch information
plokhotnyuk committed Feb 13, 2020
1 parent 489c8bd commit 0fbda65
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ArrayOfYearsReading extends ArrayOfYearsBenchmark {
def jsoniterScala(): Array[Year] = readFromArray[Array[Year]](jsonBytes)

@Benchmark
def playJson(): Array[Year] = Json.parse(jsonBytes).as[Array[Year]](yearArrayFormat)
def playJson(): Array[Year] = Json.parse(jsonBytes).as[Array[Year]]

@Benchmark
def sprayJson(): Array[Year] = JsonParser(jsonBytes).convertTo[Array[Year]]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,38 @@ object PlayJsonFormats {

implicit val intKeyReads: KeyReads[Int] = s => try JsSuccess(s.toInt) catch { case NonFatal(x) => JsError(x.getMessage) }
implicit val intKeyWrites: KeyWrites[Int] = _.toString
implicit def mutableReads[A, B](implicit aReads: Reads[Map[A, B]]): Reads[mutable.Map[A, B]] =
Reads[mutable.Map[A, B]](js => JsSuccess(js.as[Map[A, B]].foldLeft(mutable.Map.empty[A, B])((m, p) => m += ((p._1, p._2)))))
implicit val longKeyReads: KeyReads[Long] = s => try JsSuccess(s.toLong) catch { case NonFatal(x) => JsError(x.getMessage) }

implicit def mutableMapReads[A, B](implicit mapReads: Reads[Map[A, B]]): Reads[mutable.Map[A, B]] =
Reads[mutable.Map[A, B]](js => JsSuccess(js.as[Map[A, B]].foldLeft(mutable.Map.empty[A, B]) {
(m, p) => m += ((p._1, p._2))
}))

implicit def mutableLongMapReads[A](implicit mapReads: Reads[Map[Long, A]]): Reads[mutable.LongMap[A]] =
Reads[mutable.LongMap[A]](js => JsSuccess(js.as[Map[Long, A]].foldLeft(mutable.LongMap.empty[A]) {
(m, p) => m += (p._1, p._2)
}))

implicit def mutableLongMapWrites[A](implicit aWrites: Writes[A]): OWrites[mutable.LongMap[A]] =
OWrites[mutable.LongMap[A]](m => Json.toJsObject(m.foldLeft(mutable.LinkedHashMap.empty[String, JsValue]) {
(m, p) => m += ((p._1.toString, aWrites.writes(p._2)))
}))

implicit def intMapReads[A](implicit mapReads: Reads[Map[Int, A]]): Reads[IntMap[A]] =
Reads[IntMap[A]](js => JsSuccess(js.as[Map[Int, A]].foldLeft(IntMap.empty[A])((m, p) => m.updated(p._1, p._2))))

implicit def intMapWrites[A](implicit aWrites: Writes[A]): OWrites[IntMap[A]] =
OWrites[IntMap[A]](m => Json.toJsObject(m.foldLeft(mutable.LinkedHashMap.empty[String, JsValue]) {
(m, p) => m += ((p._1.toString, aWrites.writes(p._2)))
}))

// Allow case classes with Tuple2 types to be represented as a Json Array with 2 elements e.g. (Double, Double)
// Borrowed from https://gist.github.com/alexanderjarvis/4595298
implicit def tuple2Reads[A, B](implicit aReads: Reads[A], bReads: Reads[B]): Reads[Tuple2[A, B]] =
(json: JsValue) => Try {
val JsArray(arr) = json
aReads.reads(arr(0)).flatMap(a => bReads.reads(arr(1)).map(b => (a, b)))
}.getOrElse(JsError("Expected array of two elements, but got: " + json))
}.getOrElse(JsError("Expected array of two elements"))

implicit def tuple2Writes[A, B](implicit aWrites: Writes[A], bWrites: Writes[B]): Writes[Tuple2[A, B]] =
(tuple: Tuple2[A, B]) => JsArray(Seq(aWrites.writes(tuple._1), bWrites.writes(tuple._2)))
Expand Down Expand Up @@ -72,12 +95,6 @@ object PlayJsonFormats {
implicit val mutableBitSetFormat: Format[mutable.BitSet] = Format(
Reads(js => JsSuccess(mutable.BitSet.fromBitMaskNoCopy(toBitMask(js.as[Array[Int]], Int.MaxValue /* WARNING: don't do this for open-systems */)))),
Writes((es: mutable.BitSet) => JsArray(es.toArray.map(v => JsNumber(BigDecimal(v))))))
implicit val intMapOfBooleansFormat: OFormat[IntMap[Boolean]] = OFormat(
Reads[IntMap[Boolean]](js => JsSuccess(js.as[Map[String, Boolean]].foldLeft(IntMap.empty[Boolean])((m, p) => m.updated(p._1.toInt, p._2)))),
OWrites[IntMap[Boolean]](m => Json.toJsObject(m.foldLeft(mutable.LinkedHashMap.empty[String, Boolean])((m, p) => m += ((p._1.toString, p._2))))))
implicit val mutableLongMapOfBooleansFormat: OFormat[mutable.LongMap[Boolean]] = OFormat(
Reads[mutable.LongMap[Boolean]](js => JsSuccess(js.as[Map[String, Boolean]].foldLeft(new mutable.LongMap[Boolean])((m, p) => m += (p._1.toLong, p._2)))),
OWrites[mutable.LongMap[Boolean]](m => Json.toJsObject(m.foldLeft(mutable.LinkedHashMap.empty[String, Boolean])((m, p) => m += ((p._1.toString, p._2))))))
implicit val primitivesFormat: OFormat[Primitives] = Json.format
implicit val extractFieldsFormat: OFormat[ExtractFields] = Json.format
val adtFormat: OFormat[ADTBase] = {
Expand Down Expand Up @@ -134,58 +151,43 @@ object PlayJsonFormats {
implicit val v21: Format[OpenRTB.Reqs] = Jsonx.formatCaseClassUseDefaults
Json.format
}
implicit val twitterAPIFormat: Format[Seq[TwitterAPI.Tweet]] = {
implicit val twitterFormat: Format[TwitterAPI.Tweet] = {
implicit val v1: OFormat[TwitterAPI.Urls] = Json.format
implicit val v2: OFormat[TwitterAPI.Url] = Json.format
implicit val v3: OFormat[TwitterAPI.UserEntities] = Json.format
implicit val v4: OFormat[TwitterAPI.UserMentions] = Json.format
implicit val v5: OFormat[TwitterAPI.Entities] = Json.format
implicit val v6: Format[TwitterAPI.User] = Jsonx.formatCaseClass
implicit val v7: Format[TwitterAPI.RetweetedStatus] = Jsonx.formatCaseClass
implicit val v8: Format[TwitterAPI.Tweet] = Jsonx.formatCaseClass
Format(
Reads[Seq[TwitterAPI.Tweet]](js => JsSuccess(js.as[Seq[JsObject]].map(_.as[TwitterAPI.Tweet]))),
Writes[Seq[TwitterAPI.Tweet]](ts => JsArray(ts.map(t => Json.toJson(t)))))
}
implicit val enumArrayFormat: Format[Array[SuitEnum]] = {
implicit val v1: Format[SuitEnum] = Format(Reads.enumNameReads(SuitEnum), Writes.enumNameWrites)
Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(_.as[SuitEnum]))),
Writes(es => JsArray(es.map(t => Json.toJson(t)))))
Jsonx.formatCaseClass
}
implicit val enumADTArrayFormat: Format[Array[SuitADT]] =
Format(
Reads(js => Try(js.as[Array[JsString]].map {
val suite = Map(
"Hearts" -> Hearts,
"Spades" -> Spades,
"Diamonds" -> Diamonds,
"Clubs" -> Clubs)
s => suite(s.value)
}).fold[JsResult[Array[SuitADT]]](_ => JsError("SuitADT"), s => JsSuccess(s))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val javaEnumArrayFormat: Format[Array[Suit]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => Suit.valueOf(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.name)))))
implicit val charArrayFormat: Format[Array[Char]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(_.value.charAt(0)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val bigIntArrayFormat: Format[Array[BigInt]] = Format(
Reads(js => Try(js.as[Array[JsNumber]].map(_.value.toBigIntExact.get)).fold[JsResult[Array[BigInt]]](_ => JsError("BigInt"), s => JsSuccess(s))),
Writes(es => JsArray(es.map(v => JsNumber(BigDecimal(v))))))
implicit val monthDayArrayFormat: Format[Array[MonthDay]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => MonthDay.parse(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val offsetTimeArrayFormat: Format[Array[OffsetTime]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => OffsetTime.parse(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val yearArrayFormat: Format[Array[Year]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => Year.parse(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val yearMonthArrayFormat: Format[Array[YearMonth]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => YearMonth.parse(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val zoneOffsetArrayFormat: Format[Array[ZoneOffset]] = Format(
Reads(js => JsSuccess(js.as[Array[JsString]].map(js => ZoneOffset.of(js.value)))),
Writes(es => JsArray(es.map(v => JsString(v.toString)))))
implicit val enumFormat: Format[SuitEnum] = Format(Reads.enumNameReads(SuitEnum), Writes.enumNameWrites)
implicit val enumADTFormat: Format[SuitADT] = Format(
Reads {
val suite = Map(
"Hearts" -> Hearts,
"Spades" -> Spades,
"Diamonds" -> Diamonds,
"Clubs" -> Clubs)
js: JsValue => suite.get(js.as[String]).fold[JsResult[SuitADT]](JsError("Expected SuitADT"))(JsSuccess(_))
},
Writes(v => JsString(v.toString)))
implicit val javaEnumFormat: Format[Suit] = Format(
Reads(js => try JsSuccess(Suit.valueOf(js.as[String])) catch { case NonFatal(_) => JsError("Expected Suit") }),
Writes(v => JsString(v.name)))
implicit val monthDayFormat: Format[MonthDay] = Format(
Reads(js => try JsSuccess(MonthDay.parse(js.as[String])) catch { case NonFatal(_) => JsError("Expected MonthDay") }),
Writes(v => JsString(v.toString)))
implicit val offsetTimeFormat: Format[OffsetTime] = Format(
Reads(js => try JsSuccess(OffsetTime.parse(js.as[String])) catch { case NonFatal(_) => JsError("Expected OffsetTime") }),
Writes(v => JsString(v.toString)))
implicit val yearFormat: Format[Year] = Format(
Reads(js => try JsSuccess(Year.parse(js.as[String])) catch { case NonFatal(_) => JsError("Expected Year") }),
Writes(v => JsString(v.toString)))
implicit val yearMonthFormat: Format[YearMonth] = Format(
Reads(js => try JsSuccess(YearMonth.parse(js.as[String])) catch { case NonFatal(_) => JsError("Expected YearMonth") }),
Writes(v => JsString(v.toString)))
implicit val zoneOffsetFormat: Format[ZoneOffset] = Format(
Reads(js => try JsSuccess(ZoneOffset.of(js.as[String])) catch { case NonFatal(_) => JsError("Expected ZoneOffset") }),
Writes(v => JsString(v.toString)))
}

0 comments on commit 0fbda65

Please sign in to comment.