Skip to content

Commit 213db84

Browse files
author
cchantep
committed
WIP
1 parent bf10151 commit 213db84

5 files changed

Lines changed: 128 additions & 89 deletions

File tree

play-json/shared/src/main/scala/play/api/libs/json/Format.scala

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,26 +32,27 @@ object OFormat {
3232

3333
implicit def GenericOFormat[T](implicit fjs: Reads[T], tjs: OWrites[T]): Format[T] = apply(fjs, tjs)
3434

35-
def apply[A](read: JsValue => JsResult[A], write: A => JsObject): OFormat[A] = new OFormat[A] {
35+
def apply[A](read: JsValue => JsResult[A], write: A => JsObject): OFormat[A] =
36+
new FunctionalOFormat[A](read, write)
3637

37-
def reads(js: JsValue): JsResult[A] = read(js)
38+
def apply[A](r: Reads[A], w: OWrites[A]): OFormat[A] =
39+
new FunctionalOFormat[A](r.reads(_), w.writes(_))
3840

39-
def writes(a: A): JsObject = write(a)
41+
// ---
4042

41-
}
42-
43-
def apply[A](r: Reads[A], w: OWrites[A]): OFormat[A] = new OFormat[A] {
44-
def reads(js: JsValue): JsResult[A] = r.reads(js)
45-
46-
def writes(a: A): JsObject = w.writes(a)
43+
private[json] final class FunctionalOFormat[A](
44+
r: JsValue => JsResult[A],
45+
w: A => JsObject
46+
) extends OFormat[A] {
47+
def reads(js: JsValue): JsResult[A] = r(js)
48+
def writes(a: A): JsObject = w(a)
4749
}
4850
}
4951

5052
/**
5153
* Default Json formatters.
5254
*/
5355
object Format extends PathFormat with ConstraintFormat with DefaultFormat {
54-
5556
val constraints: ConstraintFormat = this
5657
val path: PathFormat = this
5758

@@ -61,20 +62,23 @@ object Format extends PathFormat with ConstraintFormat with DefaultFormat {
6162
Format(fa.map(f1), Writes(b => fa.writes(f2(b))))
6263
}
6364

64-
def apply[A](fjs: Reads[A], tjs: Writes[A]): Format[A] = new Format[A] {
65-
def reads(json: JsValue) = fjs.reads(json)
66-
def writes(o: A) = tjs.writes(o)
65+
def apply[A](fjs: Reads[A], tjs: Writes[A]): Format[A] =
66+
new FunctionalFormat[A](fjs, tjs)
67+
68+
// ---
69+
70+
private[json] final class FunctionalFormat[A](
71+
r: Reads[A],
72+
w: Writes[A]
73+
) extends Format[A] {
74+
def reads(json: JsValue) = r.reads(json)
75+
def writes(o: A) = w.writes(o)
6776
}
6877
}
6978

7079
/**
7180
* Default Json formatters.
7281
*/
7382
trait DefaultFormat {
74-
75-
implicit def GenericFormat[T](implicit fjs: Reads[T], tjs: Writes[T]): Format[T] = new Format[T] {
76-
def reads(json: JsValue) = fjs.reads(json)
77-
def writes(o: T) = tjs.writes(o)
78-
}
83+
implicit def GenericFormat[T](implicit fjs: Reads[T], tjs: Writes[T]): Format[T] = Format[T](fjs, tjs)
7984
}
80-

play-json/shared/src/main/scala/play/api/libs/json/JsMacroImpl.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -724,10 +724,11 @@ import scala.reflect.macros.blackbox
724724

725725
if (implTpeName.startsWith(forwardPrefix) ||
726726
(implTpeName.startsWith("play.api.libs.json") &&
727-
!implTpeName.contains("MacroSpec"))) {
727+
!(implTpeName.startsWith("play.api.libs.json.Functional") ||
728+
implTpeName.contains("MacroSpec")))) {
728729
impl // Avoid extra check for builtin formats
729730
} else {
730-
q"""_root_.java.util.Objects.requireNonNull($impl, "Invalid implicit resolution (forward reference?) for '" + $cn + "': " + ${implTpeName})"""
731+
q"""_root_.java.util.Objects.requireNonNull($impl, "Implicit value for '" + $cn + "' was null (forward reference?): " + ${implTpeName})"""
731732
}
732733
}
733734

play-json/shared/src/main/scala/play/api/libs/json/Reads.scala

Lines changed: 76 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import scala.annotation.implicitNotFound
88

99
import scala.util.control
1010

11-
import scala.collection.Seq
11+
import scala.collection.{ generic, Seq }
1212
import scala.collection.compat._
1313
import scala.collection.immutable.Map
1414
import scala.collection.mutable.Builder
@@ -144,14 +144,16 @@ object Reads extends ConstraintReads with PathReads with DefaultReads with Gener
144144

145145
def map[A, B](m: Reads[A], f: A => B): Reads[B] = m.map(f)
146146

147-
def apply[A, B](mf: Reads[A => B], ma: Reads[A]): Reads[B] = new Reads[B] { def reads(js: JsValue) = applicativeJsResult(mf.reads(js), ma.reads(js)) }
148-
147+
def apply[A, B](mf: Reads[A => B], ma: Reads[A]): Reads[B] = new Reads[B] {
148+
def reads(js: JsValue): JsResult[B] =
149+
applicativeJsResult(mf.reads(js), ma.reads(js))
150+
}
149151
}
150152

151153
implicit def alternative(implicit a: Applicative[Reads]): Alternative[Reads] = new Alternative[Reads] {
152154
val app = a
153155
def |[A, B >: A](alt1: Reads[A], alt2: Reads[B]): Reads[B] = new Reads[B] {
154-
def reads(js: JsValue) = alt1.reads(js) match {
156+
def reads(js: JsValue): JsResult[B] = alt1.reads(js) match {
155157
case r @ JsSuccess(_, _) => r
156158
case JsError(es1) => alt2.reads(js) match {
157159
case r2 @ JsSuccess(_, _) => r2
@@ -160,13 +162,14 @@ object Reads extends ConstraintReads with PathReads with DefaultReads with Gener
160162
}
161163
}
162164

163-
def empty: Reads[Nothing] =
164-
new Reads[Nothing] { def reads(js: JsValue) = JsError(Seq()) }
165-
165+
def empty: Reads[Nothing] = NothingReads
166166
}
167167

168+
/**
169+
* Returns an instance which uses `f` as [[Reads.reads]] function.
170+
*/
168171
def apply[A](f: JsValue => JsResult[A]): Reads[A] =
169-
new Reads[A] { def reads(json: JsValue) = f(json) }
172+
new Reads[A] { def reads(js: JsValue) = f(js) }
170173

171174
implicit def functorReads(implicit a: Applicative[Reads]) = new Functor[Reads] {
172175
def fmap[A, B](reads: Reads[A], f: A => B): Reads[B] = a.map(reads, f)
@@ -187,44 +190,6 @@ object Reads extends ConstraintReads with PathReads with DefaultReads with Gener
187190
implicit val JsArrayReducer = Reducer[JsValue, JsArray](js => JsArray(Array(js)))
188191
}
189192

190-
/**
191-
* Low priority reads.
192-
*
193-
* This exists as a compiler performance optimization, so that the compiler doesn't have to rule them out when
194-
* DefaultReads provides a simple match.
195-
*
196-
* See https://github.com/playframework/playframework/issues/4313 for more details.
197-
*/
198-
trait LowPriorityDefaultReads extends EnvReads {
199-
200-
/**
201-
* Generic deserializer for collections types.
202-
*/
203-
implicit def traversableReads[F[_], A](implicit bf: Factory[A, F[A]], ra: Reads[A]) = new Reads[F[A]] {
204-
def reads(json: JsValue) = json match {
205-
case JsArray(ts) =>
206-
207-
type Errors = Seq[(JsPath, Seq[JsonValidationError])]
208-
def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }
209-
210-
ts.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[A]]) {
211-
case (acc, (elt, idx)) => (acc, ra.reads(elt)) match {
212-
case (Right(vs), JsSuccess(v, _)) => Right(vs :+ v)
213-
case (Right(_), JsError(e)) => Left(locate(e, idx))
214-
case (Left(e), _: JsSuccess[_]) => Left(e)
215-
case (Left(e1), JsError(e2)) => Left(e1 ++ locate(e2, idx))
216-
}
217-
}.fold(JsError.apply, { res =>
218-
val builder = bf.newBuilder
219-
builder.sizeHint(res)
220-
builder ++= res
221-
JsSuccess(builder.result())
222-
})
223-
case _ => JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.jsarray"))))
224-
}
225-
}
226-
}
227-
228193
/**
229194
* Default deserializer type classes.
230195
*/
@@ -382,16 +347,17 @@ trait DefaultReads extends LowPriorityDefaultReads {
382347
*
383348
* @param enum a `scala.Enumeration`.
384349
*/
385-
def enumNameReads[E <: Enumeration](enum: E): Reads[E#Value] = new Reads[E#Value] {
386-
def reads(json: JsValue) = json match {
387-
case JsString(str) =>
388-
enum.values
389-
.find(_.toString == str)
390-
.map(JsSuccess(_))
391-
.getOrElse(JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.validenumvalue")))))
392-
case _ => JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.enumstring"))))
350+
def enumNameReads[E <: Enumeration](enum: E): Reads[E#Value] =
351+
new Reads[E#Value] {
352+
def reads(json: JsValue): JsResult[E#Value] = json match {
353+
case JsString(str) =>
354+
enum.values
355+
.find(_.toString == str)
356+
.map(JsSuccess(_))
357+
.getOrElse(JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.validenumvalue")))))
358+
case _ => JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.enumstring"))))
359+
}
393360
}
394-
}
395361

396362
/**
397363
* Deserializer for Boolean types.
@@ -535,9 +501,11 @@ trait DefaultReads extends LowPriorityDefaultReads {
535501
/**
536502
* Deserializer for Array[T] types.
537503
*/
538-
implicit def ArrayReads[T: Reads: ClassTag]: Reads[Array[T]] = new Reads[Array[T]] {
539-
def reads(json: JsValue) = json.validate[List[T]].map(_.toArray)
540-
}
504+
implicit def ArrayReads[T: Reads: ClassTag]: Reads[Array[T]] =
505+
new Reads[Array[T]] {
506+
def reads(js: JsValue): JsResult[Array[T]] =
507+
js.validate[List[T]].map(_.toArray)
508+
}
541509

542510
/**
543511
* Deserializer for java.util.UUID
@@ -567,4 +535,54 @@ trait DefaultReads extends LowPriorityDefaultReads {
567535
}
568536

569537
implicit val uuidReads: Reads[java.util.UUID] = new UUIDReader(false)
538+
539+
private[json] object NothingReads extends Reads[Nothing] {
540+
def reads(js: JsValue) = JsError(Seq.empty)
541+
}
542+
}
543+
544+
/**
545+
* Low priority reads.
546+
*
547+
* This exists as a compiler performance optimization, so that the compiler doesn't have to rule them out when
548+
* DefaultReads provides a simple match.
549+
*
550+
* See https://github.com/playframework/playframework/issues/4313 for more details.
551+
*/
552+
trait LowPriorityDefaultReads extends EnvReads {
553+
554+
/**
555+
* Generic deserializer for collections types.
556+
*/
557+
implicit def traversableReads[F[_], A](implicit bf: generic.CanBuildFrom[F[_], A, F[A]], ra: Reads[A]): Reads[F[A]] = new Reads[F[A]] {
558+
def reads(json: JsValue): JsResult[F[A]] = json match {
559+
case JsArray(ts) => {
560+
type Errors = Seq[(JsPath, Seq[JsonValidationError])]
561+
def locate(e: Errors, idx: Int) = e.map { case (p, valerr) => (JsPath(idx)) ++ p -> valerr }
562+
563+
ts.iterator.zipWithIndex.foldLeft(Right(Vector.empty): Either[Errors, Vector[A]]) {
564+
case (acc, (elt, idx)) => (acc, ra.reads(elt)) match {
565+
case (Right(vs), JsSuccess(v, _)) => Right(vs :+ v)
566+
case (Right(_), JsError(e)) => Left(locate(e, idx))
567+
case (Left(e), _: JsSuccess[_]) => Left(e)
568+
case (Left(e1), JsError(e2)) => Left(e1 ++ locate(e2, idx))
569+
}
570+
}.fold(JsError.apply, { res =>
571+
val builder = bf()
572+
builder.sizeHint(res)
573+
builder ++= res
574+
JsSuccess(builder.result())
575+
})
576+
}
577+
578+
case _ => JsError(Seq(JsPath -> Seq(JsonValidationError("error.expected.jsarray"))))
579+
}
580+
}
581+
582+
// ---
583+
584+
protected[json] final class FunctionalReads[A](
585+
r: JsValue => JsResult[A]) extends Reads[A] {
586+
def reads(v: JsValue): JsResult[A] = r(v)
587+
}
570588
}

play-json/shared/src/main/scala/play/api/libs/json/Writes.scala

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,10 @@ object OWrites extends PathWrites with ConstraintWrites {
118118

119119
}
120120

121-
def apply[A](f: A => JsObject): OWrites[A] = new OWrites[A] {
122-
def writes(a: A): JsObject = f(a)
123-
}
121+
/**
122+
* Returns an instance which uses `f` as [[OWrites.writes]] function.
123+
*/
124+
def apply[A](f: A => JsObject): OWrites[A] = new FunctionalOWrites[A](f)
124125

125126
/**
126127
* Transforms the resulting [[JsObject]] using the given function,
@@ -133,6 +134,13 @@ object OWrites extends PathWrites with ConstraintWrites {
133134
*/
134135
def transform[A](w: OWrites[A])(f: (A, JsObject) => JsObject): OWrites[A] =
135136
OWrites[A] { a => f(a, w.writes(a)) }
137+
138+
// ---
139+
140+
private[json] final class FunctionalOWrites[A](
141+
w: A => JsObject) extends OWrites[A] {
142+
def writes(a: A): JsObject = w(a)
143+
}
136144
}
137145

138146
/**
@@ -149,9 +157,10 @@ object Writes extends PathWrites with ConstraintWrites with DefaultWrites with G
149157
wa.contramap[B](f)
150158
}
151159

152-
def apply[A](f: A => JsValue): Writes[A] = new Writes[A] {
153-
def writes(a: A): JsValue = f(a)
154-
}
160+
/**
161+
* Returns an instance which uses `f` as [[Writes.writes]] function.
162+
*/
163+
def apply[A](f: A => JsValue): Writes[A] = new FunctionalWrites[A](f)
155164

156165
/**
157166
* Transforms the resulting [[JsValue]] using the given function,
@@ -164,6 +173,13 @@ object Writes extends PathWrites with ConstraintWrites with DefaultWrites with G
164173
*/
165174
def transform[A](w: Writes[A])(f: (A, JsValue) => JsValue): Writes[A] =
166175
Writes[A] { a => f(a, w.writes(a)) }
176+
177+
// ---
178+
179+
private[json] final class FunctionalWrites[A](
180+
w: A => JsValue) extends Writes[A] {
181+
def writes(a: A): JsValue = w(a)
182+
}
167183
}
168184

169185
/**

play-json/shared/src/test/scala/play/api/libs/json/MacroSpec.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ class MacroSpec extends WordSpec with MustMatchers
164164
jsLorem.validate[Lorem[Simple]]
165165
} catch {
166166
case NonFatal(npe: NullPointerException) => {
167-
val expected = "Invalid implicit resolution"
167+
val expected = "Implicit value for 'ipsum'"
168168
npe.getMessage.take(expected.size) mustEqual expected
169169
}
170170

@@ -182,7 +182,7 @@ class MacroSpec extends WordSpec with MustMatchers
182182
jsLorem.validate[Lorem[Simple]]
183183
} catch {
184184
case NonFatal(npe: NullPointerException) => {
185-
val expected = "Invalid implicit resolution"
185+
val expected = "Implicit value for 'ipsum'"
186186
npe.getMessage.take(expected.size) mustEqual expected
187187
}
188188

@@ -674,7 +674,7 @@ class MacroSpec extends WordSpec with MustMatchers
674674
Json.toJson(Lorem(age = 11, ipsum = Simple(bar = "foo")))
675675
} catch {
676676
case NonFatal(npe: NullPointerException) => {
677-
val expected = "Invalid implicit resolution"
677+
val expected = "Implicit value for 'ipsum'"
678678
npe.getMessage.take(expected.size) mustEqual expected
679679
}
680680

@@ -690,7 +690,7 @@ class MacroSpec extends WordSpec with MustMatchers
690690
Json.toJson(Lorem(age = 11, ipsum = Simple(bar = "foo")))
691691
} catch {
692692
case NonFatal(npe: NullPointerException) => {
693-
val expected = "Invalid implicit resolution"
693+
val expected = "Implicit value for 'ipsum'"
694694
npe.getMessage.take(expected.size) mustEqual expected
695695
}
696696

0 commit comments

Comments
 (0)