-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
JsonUtil.scala
155 lines (120 loc) · 5.95 KB
/
JsonUtil.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
package japgolly.webapputil.circe
import io.circe._
import io.circe.syntax._
import japgolly.microlibs.utils.StaticLookupFn
import japgolly.webapputil.general.ErrorMsg
import japgolly.univeq._
object JsonUtil {
object Implicits extends Implicits
trait Implicits
extends CirceExtensions
with UnivEqInstances
// ===================================================================================================================
object UnivEqInstances extends UnivEqInstances
trait UnivEqInstances {
implicit def univEqCirceDecodingFailure: UnivEq[DecodingFailure] = UnivEq.force
implicit def univEqCirceError : UnivEq[io.circe.Error] = UnivEq.force
implicit def univEqCirceJson : UnivEq[Json] = UnivEq.force
implicit def univEqCirceJsonObject : UnivEq[JsonObject] = UnivEq.force
}
// ===================================================================================================================
trait CirceExtensions {
import CirceExtensions._
implicit def webappUtilCirceExtEncoderObject(a: Encoder.type): WebappUtilCirceExtEncoderObject =
new WebappUtilCirceExtEncoderObject(a)
implicit def WebappUtilCirceExtDecoderF[F[_], A](a: Decoder[F[A]]): WebappUtilCirceExtDecoderF[F, A] =
new WebappUtilCirceExtDecoderF(a)
implicit def webappUtilCirceExtHCursor(a: HCursor): WebappUtilCirceExtHCursor =
new WebappUtilCirceExtHCursor(a)
implicit def webappUtilCirceExtJsonObject(a: JsonObject): WebappUtilCirceExtJsonObject =
new WebappUtilCirceExtJsonObject(a)
}
object CirceExtensions extends CirceExtensions {
class WebappUtilCirceExtEncoderObject(private val self: Encoder.type) extends AnyVal {
def instanceObject[A](f: A => JsonObject): Encoder[A] =
Encoder.instance[A](a => Json.fromJsonObject(f(a)))
}
class WebappUtilCirceExtDecoderF[F[_], A](private val self: Decoder[F[A]]) extends AnyVal {
def orLiftOne(f: A => F[A])(implicit A: Decoder[A]): Decoder[F[A]] =
Decoder.instance[F[A]] { c =>
val rf = self(c)
if (rf.isRight)
rf
else {
val ra = A(c)
if (ra.isRight) ra.map(f) else rf
}
}
}
class WebappUtilCirceExtHCursor(private val self: HCursor) extends AnyVal {
def decodeSomeOrOne[F[_], A](one: A => F[A])
(implicit F: Decoder[F[A]], A: Decoder[A]): Decoder.Result[F[A]] =
F.orLiftOne(one)(A)(self)
def getOption[A: Decoder](key: String, nullIsNone: Boolean = true): Decoder.Result[Option[A]] = {
val f = self.downField(key)
f.focus match {
case None => Right(None)
case Some(j) if nullIsNone && j.isNull => Right(None)
case Some(_) => f.as[A].map(Some.apply)
}
}
def getOrDefault[A: Decoder](key: String, default: A, nullIsNone: Boolean = true): Decoder.Result[A] =
getOption(key, nullIsNone = nullIsNone).map(_.getOrElse(default))
def getSoleField[A: Decoder](key: String): Decoder.Result[A] =
self.keys match {
case Some(keys) =>
keys.iterator.take(2).toList match {
case k :: Nil if k ==* key => self.downField(key).as[A]
case _ => Left(DecodingFailure(s"Exactly one key ($key) required in ${self.focus}", self.history))
}
case None => Left(DecodingFailure("Object expected.", self.history))
}
def getSomeOrOne[F[_], A](key: String, one: A => F[A])
(implicit F: Decoder[F[A]], A: Decoder[A]): Decoder.Result[F[A]] =
self.get(key)(F.orLiftOne(one))
}
class WebappUtilCirceExtJsonObject(private val self: JsonObject) extends AnyVal {
def addOption[A: Encoder](key: String, value: Option[A]): JsonObject =
value.fold(self)(a => self.add(key, a.asJson))
def addWhen[A: Encoder](key: String, value: A)(test: A => Boolean): JsonObject =
if (test(value)) self.add(key, value.asJson) else self
}
}
// ===================================================================================================================
def decoderFnSumBySoleKey[A](f: PartialFunction[(String, ACursor), Decoder.Result[A]]): ACursor => Decoder.Result[A] = {
def keyErr = "Expected a single key indicating the subtype"
c =>
c.keys match {
case Some(it) =>
it.toList match {
case singleKey :: Nil =>
val arg = (singleKey, c.downField(singleKey))
def fail = Left(DecodingFailure("Unknown subtype: " + singleKey, c.history))
f.applyOrElse(arg, (_: (String, ACursor)) => fail)
case Nil => Left(DecodingFailure(keyErr, c.history))
case keys => Left(DecodingFailure(s"$keyErr, found multiple: $keys", c.history))
}
case None => Left(DecodingFailure(keyErr, c.history))
}
}
def decodeSumBySoleKey[A](f: PartialFunction[(String, ACursor), Decoder.Result[A]]): Decoder[A] =
Decoder.instance(decoderFnSumBySoleKey(f))
def decodeSumBySoleKeyOrConst[A](consts: (String, A)*)(f: PartialFunction[(String, ACursor), Decoder.Result[A]]): Decoder[A] =
decodeSumBySoleKeyOr(consts: _*)(decoderFnSumBySoleKey(f))
def decodeSumBySoleKeyOr[A](consts: (String, A)*)(orElse: ACursor => Decoder.Result[A]): Decoder[A] = {
val lookup = StaticLookupFn.useMap(consts).toOption
Decoder.instance(c =>
c.as[String].toOption.flatMap(lookup) match {
case Some(r) => Right(r)
case None => orElse(c)
}
)
}
def decodingFailureMsg(f: DecodingFailure): ErrorMsg =
ErrorMsg(s"Failed to decode JSON at ${CursorOp.opsToPath(f.history)}: ${f.message}")
def errorMsg(e: io.circe.Error): ErrorMsg =
e match {
case f: ParsingFailure => ErrorMsg(s"Failed to parse JSON: ${f.message}")
case f: DecodingFailure => decodingFailureMsg(f)
}
}