Say we have a morally correct json library.
It comes with types like: JValue
, JObject
and Path
.
It also has error types extending JsonError
like ParsingError
, CastingError
and FieldError
. To make it even smarter, there's a sweet Semigroup[JsonError]
to accumulate errors properly (left as an exercise).
Finally, there are a bunch of basic combinators:
def parse(in: String): ParsingError \/ JValue
def toJObject(jValue: JValue): CastingError \/ JObject
def toSeq(jValue: JValue): CastingError \/ Seq[JValue]
def field(jObject: JObject, path: Path): FieldError \/ JValue
We can now write:
def run(input: String): JsonError \/ (JObject, Seq[JValue]) =
parse(input)
.flatMap(toJObject)
.flatMap { jObject =>
val meta = field(jObject, "meta").flatMap(toJObject).validation
val body = field(jObject, "body").flatMap(toSeq).validation
meta.tuple(body).disjunction
}
Or with a for comprehension:
def run(input: String): JsonError \/ (JObject, Seq[JValue]) =
for {
json <- parse(input)
jObject <- toJObject(json)
result <- {
val meta = field(jObject, "meta").flatMap(toJObject).validation
val body = field(jObject, "body").flatMap(toSeq).validation
meta.tuple(body).disjunction
}
} yield result