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 \/ JValueWe 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