From 0fbda655984e24f26a9e9174944b9e70430fa3b5 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Thu, 13 Feb 2020 18:28:46 +0100 Subject: [PATCH] Clean up derivation of Play-JSON's `Reads` and `Writes` --- .../benchmark/ArrayOfYearsReading.scala | 2 +- .../benchmark/PlayJsonFormats.scala | 112 +++++++++--------- 2 files changed, 58 insertions(+), 56 deletions(-) diff --git a/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/ArrayOfYearsReading.scala b/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/ArrayOfYearsReading.scala index d5c25778e..661390f20 100644 --- a/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/ArrayOfYearsReading.scala +++ b/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/ArrayOfYearsReading.scala @@ -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]] diff --git a/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/PlayJsonFormats.scala b/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/PlayJsonFormats.scala index 41447e21f..704037e95 100644 --- a/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/PlayJsonFormats.scala +++ b/jsoniter-scala-benchmark/src/main/scala/com/github/plokhotnyuk/jsoniter_scala/benchmark/PlayJsonFormats.scala @@ -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))) @@ -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] = { @@ -134,7 +151,7 @@ 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 @@ -142,50 +159,35 @@ object PlayJsonFormats { 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))) } \ No newline at end of file