Util / Json / FromJson.flix
FromJson.flix
/*
* Copyright 2026 Flix Authors
*
* Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE.md file.
*/
pub mod Util.Json.FromJson {
use Util.Json
use Util.Json.Json.{JArray, JBool, JNull, JNumber, JObject, JString}
use Util.Json.JsonError
use Util.Json.JsonError.JsonError.Decode
use Util.Json.JsonError.{prependStep, prependPath}
use Util.Json.JsonError.DecodeErrorKind.{MissingIndex, MissingKey, TypeMismatch}
use Util.Json.JsonPath
use Util.Json.JsonPath.JsonStep
use Util.Json.JsonPath.{empty, fromList, steps}
///
/// A trait for types that can be decoded from a `Json` value.
///
pub trait FromJson[a] {
pub def fromJson(j: Json): Result[JsonError, a]
}
///
/// Walks `p` in `j` and returns the raw `Json` value at the leaf.
///
/// On failure the error's path points to the prefix of `p` that was
/// successfully traversed — i.e. up to but not including the failing
/// step. The failing step is reflected in the error's *kind*:
/// `MissingKey(k)` for an absent object key, `MissingIndex(i)` for an
/// out-of-range array index, and `TypeMismatch` for a step that
/// descends into the wrong shape (e.g. a `Key` into a non-object).
///
pub def getAtPath(p: JsonPath, j: Json): Result[JsonError, Json] =
getSteps(steps(p), j)
///
/// Looks up the value at `p` in `j` and decodes it via `FromJson[a]`.
///
/// Navigation failures follow the same path/kind convention as
/// `getAtPath`. If navigation succeeds and the leaf decoder fails, the
/// leaf's own inner path (e.g. `/0` from a failing `Vector` element) is
/// appended to `p`, giving the full root-to-failure path.
///
pub def decodeAtPath(p: JsonPath, j: Json): Result[JsonError, a] with FromJson[a] =
match getAtPath(p, j) {
case Ok(leaf) => match FromJson.fromJson(leaf) {
case Ok(x) => Ok(x)
case Err(e) => Err(prependPath(p, e))
}
case Err(e) => Err(e)
}
///
/// Looks up the value at `p` in `j` and, if present, decodes it via
/// `FromJson[a]`.
///
/// Missing-key and missing-index failures along `p` yield `Ok(None)`.
/// Type-mismatch failures (a `Key` step into a non-object, or an `Index`
/// step into a non-array) still error, since those signal a real shape
/// disagreement. Leaf decode failures are reported with the full
/// `p`-prefixed path.
///
pub def decodeAtPathOpt(p: JsonPath, j: Json): Result[JsonError, Option[a]] with FromJson[a] =
match getAtPath(p, j) {
case Ok(leaf) => match FromJson.fromJson(leaf) {
case Ok(x) => Ok(Some(x))
case Err(e) => Err(prependPath(p, e))
}
case Err(Decode(_, MissingKey(_))) => Ok(None)
case Err(Decode(_, MissingIndex(_))) => Ok(None)
case Err(e) => Err(e)
}
///
/// Looks up `k` in `j`, which must be a `JObject`.
///
/// Returns the raw `Json` value at `k`, `MissingKey(k)` if `k` is absent,
/// or `TypeMismatch` if `j` is not a `JObject`. Both errors carry the
/// empty path (the failure is at the level of `j` itself).
///
pub def getAtKey(k: String, j: Json): Result[JsonError, Json] =
getAtPath(fromList(JsonStep.Key(k) :: Nil), j)
///
/// Looks up `k` in `j`, which must be a `JObject`.
///
/// Returns `Some(v)` if present, `None` if absent, or `TypeMismatch` if
/// `j` is not a `JObject`.
///
pub def getAtKeyOpt(k: String, j: Json): Result[JsonError, Option[Json]] = match j {
case JObject(m) => Ok(Map.get(k, m))
case _ => Err(typeMismatch("JObject"))
}
///
/// Looks up `k` in `j` and decodes the value via `FromJson[a]`.
///
/// If `k` is absent, errors with `MissingKey(k)` at the empty path. If `k`
/// is present but the value fails to decode, the inner error's path is
/// prepended with `Key(k)`.
///
pub def decodeAtKey(k: String, j: Json): Result[JsonError, a] with FromJson[a] =
decodeAtPath(fromList(JsonStep.Key(k) :: Nil), j)
///
/// Looks up `k` in `j` and, if present, decodes the value via `FromJson[a]`.
///
/// Returns `Ok(None)` if `k` is absent, `Ok(Some(v))` if present and
/// decodable, or the decode error otherwise.
///
pub def decodeAtKeyOpt(k: String, j: Json): Result[JsonError, Option[a]] with FromJson[a] =
decodeAtPathOpt(fromList(JsonStep.Key(k) :: Nil), j)
///
/// Looks up index `i` in `j`, which must be a `JArray`, and decodes the
/// element via `FromJson[a]`.
///
/// If `i` is out of range, errors with `MissingIndex(i)` at the empty
/// path. If the element is present but fails to decode, the inner error's
/// path is prepended with `Index(i)`.
///
pub def decodeAtIndex(i: Int32, j: Json): Result[JsonError, a] with FromJson[a] =
decodeAtPath(fromList(JsonStep.Index(i) :: Nil), j)
///
/// Recursive walker for `getAtPath`: descends `j` step by step. On a
/// successful descent, prepends the consumed step to any deeper error's
/// path, so the final error path is the prefix that was traversed.
///
def getSteps(steps: List[JsonStep], j: Json): Result[JsonError, Json] = match steps {
case Nil => Ok(j)
case JsonStep.Key(k) :: rest => match j {
case JObject(m) => match Map.get(k, m) {
case None => Err(missingKey(k))
case Some(v) => Result.mapErr(prependStep(JsonStep.Key(k)), getSteps(rest, v))
}
case _ => Err(typeMismatch("JObject"))
}
case JsonStep.Index(i) :: rest => match j {
case JArray(v) =>
if (i < 0 or i >= Vector.length(v))
Err(missingIndex(i))
else
Result.mapErr(prependStep(JsonStep.Index(i)), getSteps(rest, Vector.get(i, v)))
case _ => Err(typeMismatch("JArray"))
}
}
///
/// Constructs a `Decode` error with `TypeMismatch(d)` at the empty path.
///
def typeMismatch(d: String): JsonError = Decode(empty(), TypeMismatch(d))
///
/// Constructs a `Decode` error with `MissingKey(k)` at the empty path.
///
def missingKey(k: String): JsonError = Decode(empty(), MissingKey(k))
///
/// Constructs a `Decode` error with `MissingIndex(i)` at the empty path.
///
def missingIndex(i: Int32): JsonError = Decode(empty(), MissingIndex(i))
///
/// Returns `true` if `d` is integer-valued — i.e. it has no fractional part.
///
/// Used by the integer `FromJson` instances to accept JSON numbers like
/// `42` and `42.0` while rejecting `42.5`.
///
def isIntegerValued(d: BigDecimal): Bool = BigDecimal.floor(d) == d
///
/// Decodes the elements of `v` via `FromJson[a]`, prepending `Index(i)` to
/// any inner error path. Stops on the first failure.
///
def traverseIndexed(v: Vector[Json]): Result[JsonError, Vector[a]] with FromJson[a] =
Vector.traverse(
match (i, x) -> Result.mapErr(prependStep(JsonStep.Index(i)), FromJson.fromJson(x)),
Vector.mapWithIndex((i, x) -> (i, x), v))
// --- Instances ---
instance FromJson[Json] {
pub def fromJson(j: Json): Result[JsonError, Json] = Ok(j)
}
instance FromJson[Unit] {
pub def fromJson(j: Json): Result[JsonError, Unit] = match j {
case JNull => Ok(())
case _ => Err(Decode(empty(), TypeMismatch("Unit")))
}
}
instance FromJson[Bool] {
pub def fromJson(j: Json): Result[JsonError, Bool] = match j {
case JBool(b) => Ok(b)
case _ => Err(Decode(empty(), TypeMismatch("Bool")))
}
}
// No `FromJson[Char]`: `Char` is a single UTF-16 code unit (BMP only), but JSON
// strings carry full Unicode. Use `String` for JSON-bound character data.
instance FromJson[String] {
pub def fromJson(j: Json): Result[JsonError, String] = match j {
case JString(s) => Ok(s)
case _ => Err(Decode(empty(), TypeMismatch("String")))
}
}
/// Integer-valued JSON numbers (e.g. `42`, `42.0`) decode; genuine fractions
/// (e.g. `42.5`) and out-of-range values error.
instance FromJson[Int8] {
pub def fromJson(j: Json): Result[JsonError, Int8] = match j {
case JNumber(d) if isIntegerValued(d) => match BigDecimal.tryToInt8(d) {
case Some(i) => Ok(i)
case None => Err(Decode(empty(), TypeMismatch("Int8")))
}
case _ => Err(Decode(empty(), TypeMismatch("Int8")))
}
}
/// Integer-valued JSON numbers (e.g. `42`, `42.0`) decode; genuine fractions
/// (e.g. `42.5`) and out-of-range values error.
instance FromJson[Int16] {
pub def fromJson(j: Json): Result[JsonError, Int16] = match j {
case JNumber(d) if isIntegerValued(d) => match BigDecimal.tryToInt16(d) {
case Some(i) => Ok(i)
case None => Err(Decode(empty(), TypeMismatch("Int16")))
}
case _ => Err(Decode(empty(), TypeMismatch("Int16")))
}
}
/// Integer-valued JSON numbers (e.g. `42`, `42.0`) decode; genuine fractions
/// (e.g. `42.5`) and out-of-range values error.
instance FromJson[Int32] {
pub def fromJson(j: Json): Result[JsonError, Int32] = match j {
case JNumber(d) if isIntegerValued(d) => match BigDecimal.tryToInt32(d) {
case Some(i) => Ok(i)
case None => Err(Decode(empty(), TypeMismatch("Int32")))
}
case _ => Err(Decode(empty(), TypeMismatch("Int32")))
}
}
/// Integer-valued JSON numbers (e.g. `42`, `42.0`) decode; genuine fractions
/// (e.g. `42.5`) and out-of-range values error.
instance FromJson[Int64] {
pub def fromJson(j: Json): Result[JsonError, Int64] = match j {
case JNumber(d) if isIntegerValued(d) => match BigDecimal.tryToInt64(d) {
case Some(i) => Ok(i)
case None => Err(Decode(empty(), TypeMismatch("Int64")))
}
case _ => Err(Decode(empty(), TypeMismatch("Int64")))
}
}
/// Integer-valued JSON numbers (e.g. `42`, `42.0`) decode; genuine fractions
/// (e.g. `42.5`) error.
instance FromJson[BigInt] {
pub def fromJson(j: Json): Result[JsonError, BigInt] = match j {
case JNumber(d) if isIntegerValued(d) => Ok(BigDecimal.toBigInt(d))
case _ => Err(Decode(empty(), TypeMismatch("BigInt")))
}
}
instance FromJson[BigDecimal] {
pub def fromJson(j: Json): Result[JsonError, BigDecimal] = match j {
case JNumber(d) => Ok(d)
case _ => Err(Decode(empty(), TypeMismatch("BigDecimal")))
}
}
/// Out-of-range values error; precision loss may occur within range.
instance FromJson[Float32] {
pub def fromJson(j: Json): Result[JsonError, Float32] = match j {
case JNumber(d) => match BigDecimal.tryToFloat32(d) {
case Some(f) => Ok(f)
case None => Err(Decode(empty(), TypeMismatch("Float32")))
}
case _ => Err(Decode(empty(), TypeMismatch("Float32")))
}
}
/// Out-of-range values error; precision loss may occur within range.
instance FromJson[Float64] {
pub def fromJson(j: Json): Result[JsonError, Float64] = match j {
case JNumber(d) => match BigDecimal.tryToFloat64(d) {
case Some(f) => Ok(f)
case None => Err(Decode(empty(), TypeMismatch("Float64")))
}
case _ => Err(Decode(empty(), TypeMismatch("Float64")))
}
}
/// `JNull` decodes to `None`; any other value is decoded with `FromJson[a]`
/// and wrapped in `Some`. The path is left unchanged — `Option` does not
/// introduce a structural step.
instance FromJson[Option[a]] with FromJson[a] {
pub def fromJson(j: Json): Result[JsonError, Option[a]] = match j {
case JNull => Ok(None)
case _ => Result.map(x -> Some(x), FromJson.fromJson(j))
}
}
instance FromJson[Vector[a]] with FromJson[a] {
pub def fromJson(j: Json): Result[JsonError, Vector[a]] = match j {
case JArray(v) => traverseIndexed(v)
case _ => Err(Decode(empty(), TypeMismatch("JArray")))
}
}
instance FromJson[List[a]] with FromJson[a] {
pub def fromJson(j: Json): Result[JsonError, List[a]] = match j {
case JArray(v) => Result.map(Vector.toList, traverseIndexed(v))
case _ => Err(Decode(empty(), TypeMismatch("JArray")))
}
}
/// Duplicate elements are deduplicated by `Order[a]`. The element type's
/// path is threaded as `Index(i)` for any decode failure.
instance FromJson[Set[a]] with FromJson[a], Order[a] {
pub def fromJson(j: Json): Result[JsonError, Set[a]] = match j {
case JArray(v) => Result.map(Vector.toSet, traverseIndexed(v))
case _ => Err(Decode(empty(), TypeMismatch("JArray")))
}
}
/// Object keys are decoded via `FromString[k]`; a key that fails to parse
/// errors with `TypeMismatch("Map key")` at path `Key(stringKey)`. Value
/// decode failures carry path `Key(stringKey)` followed by the inner path.
instance FromJson[Map[k, v]] with FromString[k], Order[k], FromJson[v] {
pub def fromJson(j: Json): Result[JsonError, Map[k, v]] = match j {
case JObject(m) =>
Map.toList(m)
|> List.traverse(match (k, jv) -> forM (
kp <- Option.toOk(Decode(fromList(JsonStep.Key(k) :: Nil), TypeMismatch("Map key")), FromString.fromString(k));
vp <- Result.mapErr(prependStep(JsonStep.Key(k)), FromJson.fromJson(jv))
) yield (kp, vp))
|> Result.map(List.toMap)
case _ => Err(Decode(empty(), TypeMismatch("JObject")))
}
}
instance FromJson[(a1, a2)] with FromJson[a1], FromJson[a2] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2)] = match j {
case JArray(v) if Vector.length(v) == 2 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)))
) yield (x1, x2)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 2")))
}
}
instance FromJson[(a1, a2, a3)] with FromJson[a1], FromJson[a2], FromJson[a3] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3)] = match j {
case JArray(v) if Vector.length(v) == 3 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)))
) yield (x1, x2, x3)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 3")))
}
}
instance FromJson[(a1, a2, a3, a4)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4)] = match j {
case JArray(v) if Vector.length(v) == 4 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)))
) yield (x1, x2, x3, x4)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 4")))
}
}
instance FromJson[(a1, a2, a3, a4, a5)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5)] = match j {
case JArray(v) if Vector.length(v) == 5 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)))
) yield (x1, x2, x3, x4, x5)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 5")))
}
}
instance FromJson[(a1, a2, a3, a4, a5, a6)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5], FromJson[a6] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5, a6)] = match j {
case JArray(v) if Vector.length(v) == 6 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)));
x6 <- Result.mapErr(prependStep(JsonStep.Index(5)), FromJson.fromJson(Vector.get(5, v)))
) yield (x1, x2, x3, x4, x5, x6)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 6")))
}
}
instance FromJson[(a1, a2, a3, a4, a5, a6, a7)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5], FromJson[a6], FromJson[a7] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5, a6, a7)] = match j {
case JArray(v) if Vector.length(v) == 7 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)));
x6 <- Result.mapErr(prependStep(JsonStep.Index(5)), FromJson.fromJson(Vector.get(5, v)));
x7 <- Result.mapErr(prependStep(JsonStep.Index(6)), FromJson.fromJson(Vector.get(6, v)))
) yield (x1, x2, x3, x4, x5, x6, x7)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 7")))
}
}
instance FromJson[(a1, a2, a3, a4, a5, a6, a7, a8)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5], FromJson[a6], FromJson[a7], FromJson[a8] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5, a6, a7, a8)] = match j {
case JArray(v) if Vector.length(v) == 8 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)));
x6 <- Result.mapErr(prependStep(JsonStep.Index(5)), FromJson.fromJson(Vector.get(5, v)));
x7 <- Result.mapErr(prependStep(JsonStep.Index(6)), FromJson.fromJson(Vector.get(6, v)));
x8 <- Result.mapErr(prependStep(JsonStep.Index(7)), FromJson.fromJson(Vector.get(7, v)))
) yield (x1, x2, x3, x4, x5, x6, x7, x8)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 8")))
}
}
instance FromJson[(a1, a2, a3, a4, a5, a6, a7, a8, a9)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5], FromJson[a6], FromJson[a7], FromJson[a8], FromJson[a9] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5, a6, a7, a8, a9)] = match j {
case JArray(v) if Vector.length(v) == 9 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)));
x6 <- Result.mapErr(prependStep(JsonStep.Index(5)), FromJson.fromJson(Vector.get(5, v)));
x7 <- Result.mapErr(prependStep(JsonStep.Index(6)), FromJson.fromJson(Vector.get(6, v)));
x8 <- Result.mapErr(prependStep(JsonStep.Index(7)), FromJson.fromJson(Vector.get(7, v)));
x9 <- Result.mapErr(prependStep(JsonStep.Index(8)), FromJson.fromJson(Vector.get(8, v)))
) yield (x1, x2, x3, x4, x5, x6, x7, x8, x9)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 9")))
}
}
instance FromJson[(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)] with FromJson[a1], FromJson[a2], FromJson[a3], FromJson[a4], FromJson[a5], FromJson[a6], FromJson[a7], FromJson[a8], FromJson[a9], FromJson[a10] {
pub def fromJson(j: Json): Result[JsonError, (a1, a2, a3, a4, a5, a6, a7, a8, a9, a10)] = match j {
case JArray(v) if Vector.length(v) == 10 =>
forM (
x1 <- Result.mapErr(prependStep(JsonStep.Index(0)), FromJson.fromJson(Vector.get(0, v)));
x2 <- Result.mapErr(prependStep(JsonStep.Index(1)), FromJson.fromJson(Vector.get(1, v)));
x3 <- Result.mapErr(prependStep(JsonStep.Index(2)), FromJson.fromJson(Vector.get(2, v)));
x4 <- Result.mapErr(prependStep(JsonStep.Index(3)), FromJson.fromJson(Vector.get(3, v)));
x5 <- Result.mapErr(prependStep(JsonStep.Index(4)), FromJson.fromJson(Vector.get(4, v)));
x6 <- Result.mapErr(prependStep(JsonStep.Index(5)), FromJson.fromJson(Vector.get(5, v)));
x7 <- Result.mapErr(prependStep(JsonStep.Index(6)), FromJson.fromJson(Vector.get(6, v)));
x8 <- Result.mapErr(prependStep(JsonStep.Index(7)), FromJson.fromJson(Vector.get(7, v)));
x9 <- Result.mapErr(prependStep(JsonStep.Index(8)), FromJson.fromJson(Vector.get(8, v)));
x10 <- Result.mapErr(prependStep(JsonStep.Index(9)), FromJson.fromJson(Vector.get(9, v)))
) yield (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)
case _ => Err(Decode(empty(), TypeMismatch("JArray of length 10")))
}
}
}