diff --git a/src/Json.flix b/src/Json.flix index b832a15..ba574ca 100644 --- a/src/Json.flix +++ b/src/Json.flix @@ -1,24 +1 @@ -mod Json { - use JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool, JsonNull} - - pub enum JsonElement { - case JsonObject(Map[String, JsonElement]) - case JsonArray(Vector[JsonElement]) - case JsonString(String) - case JsonNumber(BigDecimal) - case JsonBool(Bool) - case JsonNull - } - - instance Eq[JsonElement] { - pub def eq(x: JsonElement, y: JsonElement): Bool = match (x, y) { - case (JsonObject(a), JsonObject(b)) => a == b - case (JsonArray(a), JsonArray(b)) => a == b - case (JsonString(a), JsonString(b)) => a == b - case (JsonNumber(a), JsonNumber(b)) => a == b - case (JsonBool(a), JsonBool(b)) => a == b - case (JsonNull, JsonNull) => true - case _ => false - } - } -} \ No newline at end of file +pub mod Json {} \ No newline at end of file diff --git a/src/Jsonable.flix b/src/Json/FromJson.flix similarity index 62% rename from src/Jsonable.flix rename to src/Json/FromJson.flix index 7d978a7..ef84d61 100644 --- a/src/Jsonable.flix +++ b/src/Json/FromJson.flix @@ -1,14 +1,9 @@ -mod Json { - use Json.Path.Path; - use Json.Path.{!!}; - use JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool, JsonNull} - use JsonError.JsonError - - pub enum JsonError(Path, Set[String]) with Eq - - pub trait ToJson[a] { - pub def toJson(x: a): JsonElement - } +pub mod Json.FromJson { + use Json.JsonElement + use Json.JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool, JsonNull} + use Json.JsonError + use Json.Path + use Json.Path.{!!} pub trait FromJson[a] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, a] @@ -22,16 +17,6 @@ mod Json { pub def fromNullableJson(x: JsonElement): Result[JsonError, Option[a]] = Json.FromJson.fromNullableJsonAt(Path.Root, x) } - pub lawful trait Jsonable[a] with ToJson[a], FromJson[a] { - law inverse: forall (x: a) with Eq[a] { - x |> Json.ToJson.toJson |> Json.FromJson.fromJson == Ok(x) - } - } - - instance ToJson[Bool] { - pub def toJson(x: Bool): JsonElement = JsonBool(x) - } - instance FromJson[Bool] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Bool] = match x { case JsonBool(y) => Ok(y) @@ -39,12 +24,6 @@ mod Json { } } - instance Jsonable[Bool] - - instance ToJson[Char] { - pub def toJson(x: Char): JsonElement = JsonString(Char.toString(x)) - } - instance FromJson[Char] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Char] = match x { case JsonString(y) if String.length(y) > 0 => Ok(String.charAt(0, y)) @@ -52,28 +31,6 @@ mod Json { } } - instance Jsonable[Char] - - instance ToJson[Float32] { - pub def toJson(x: Float32): JsonElement = { - match Float32.tryToBigDecimal(x) { - case Some(num) => JsonNumber(num) - case None => - if (Float32.isInfinite(x)) { - if (x > 0.0f32) { - JsonString("Infinity") - } else { - JsonString("-Infinity") - } - } else if (Float32.isNan(x)) { - JsonString("NaN") - } else { - bug!("invalid Float32: ${x}") - } - } - } - } - instance FromJson[Float32] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Float32] = match x { case JsonNumber(y) => BigDecimal.tryToFloat32(y) |> Option.toOk(JsonError(p, Set#{"floating-point number", "\"-Infinity\"", "\"Infinity\"", "\"NaN\""})) @@ -84,26 +41,6 @@ mod Json { } } - instance ToJson[Float64] { - pub def toJson(x: Float64): JsonElement = { - match Float64.tryToBigDecimal(x) { - case Some(num) => JsonNumber(num) - case None => - if (Float64.isInfinite(x)) { - if (x > 0.0f64) { - JsonString("Infinity") - } else { - JsonString("-Infinity") - } - } else if (Float64.isNan(x)) { - JsonString("NaN") - } else { - bug!("invalid Float64: ${x}") - } - } - } - } - instance FromJson[Float64] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Float64] = match x { case JsonNumber(y) => BigDecimal.tryToFloat64(y) |> Option.toOk(JsonError(p, Set#{"floating-point number", "\"-Infinity\"", "\"Infinity\"", "\"NaN\""})) @@ -114,10 +51,6 @@ mod Json { } } - instance ToJson[BigDecimal] { - pub def toJson(x: BigDecimal): JsonElement = JsonNumber(x) - } - instance FromJson[BigDecimal] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, BigDecimal] = match x { case JsonNumber(y) => Ok(y) @@ -125,14 +58,6 @@ mod Json { } } - instance Jsonable[BigDecimal] - - instance ToJson[Int8] { - pub def toJson(x: Int8): JsonElement = { - JsonNumber(Int8.toBigDecimal(x)) - } - } - instance FromJson[Int8] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Int8] = match x { case JsonNumber(y) => BigDecimal.tryToInt8(y) |> Option.toOk(JsonError(p, Set#{"8-bit integer"})) @@ -140,14 +65,6 @@ mod Json { } } - instance Jsonable[Int8] - - instance ToJson[Int16] { - pub def toJson(x: Int16): JsonElement = { - JsonNumber(Int16.toBigDecimal(x)) - } - } - instance FromJson[Int16] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Int16] = match x { case JsonNumber(y) => BigDecimal.tryToInt16(y) |> Option.toOk(JsonError(p, Set#{"16-bit integer"})) @@ -155,14 +72,6 @@ mod Json { } } - instance Jsonable[Int16] - - instance ToJson[Int32] { - pub def toJson(x: Int32): JsonElement = { - JsonNumber(Int32.toBigDecimal(x)) - } - } - instance FromJson[Int32] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Int32] = match x { case JsonNumber(y) => BigDecimal.tryToInt32(y) |> Option.toOk(JsonError(p, Set#{"32-bit integer"})) @@ -170,14 +79,6 @@ mod Json { } } - instance Jsonable[Int32] - - instance ToJson[Int64] { - pub def toJson(x: Int64): JsonElement = { - JsonNumber(Int64.toBigDecimal(x)) - } - } - instance FromJson[Int64] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Int64] = match x { case JsonNumber(y) => BigDecimal.tryToInt64(y) |> Option.toOk(JsonError(p, Set#{"64-bit integer"})) @@ -185,14 +86,6 @@ mod Json { } } - instance Jsonable[Int64] - - instance ToJson[BigInt] { - pub def toJson(x: BigInt): JsonElement = { - JsonNumber(BigInt.toBigDecimal(x)) - } - } - instance FromJson[BigInt] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, BigInt] = match x { case JsonNumber(y) => Ok(BigDecimal.toBigInt(y)) @@ -200,12 +93,6 @@ mod Json { } } - instance Jsonable[BigInt] - - instance ToJson[String] { - pub def toJson(x: String): JsonElement = JsonString(x) - } - instance FromJson[String] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, String] = match x { case JsonString(y) => Ok(y) @@ -213,15 +100,6 @@ mod Json { } } - instance Jsonable[String] - - instance ToJson[List[a]] with ToJson[a] { - pub def toJson(x: List[a]): JsonElement = { - let elems = List.map(Json.ToJson.toJson, x) |> List.toVector; - JsonArray(elems) - } - } - instance FromJson[List[a]] with FromJson[a] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, List[a]] = match x { case JsonArray(v) => v |> Vector.toList |> List.zipWithIndex |> Result.traverse(match (i, val) -> Json.FromJson.fromJsonAt(p !! i, val)) @@ -229,15 +107,6 @@ mod Json { } } - instance Jsonable[List[a]] with Jsonable[a] - - instance ToJson[Vector[a]] with ToJson[a] { - pub def toJson(x: Vector[a]): JsonElement = { - let elems = Vector.map(Json.ToJson.toJson, x); - JsonArray(elems) - } - } - instance FromJson[Vector[a]] with FromJson[a] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Vector[a]] = match x { case JsonArray(v) => @@ -246,18 +115,6 @@ mod Json { } } - instance Jsonable[Vector[a]] with Jsonable[a] - - instance ToJson[Map[k, v]] with ToString[k], ToJson[v] { - pub def toJson(x: Map[k, v]): JsonElement = { - let map = x - |> Map.toList - |> List.map(match (k, v) -> (ToString.toString(k), Json.ToJson.toJson(v))) - |> List.toMap; - JsonObject(map) - } - } - instance FromJson[Map[k, v]] with FromString[k], Order[k], FromJson[v] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Map[k, v]] = match x { case JsonObject(m0) => @@ -278,15 +135,6 @@ mod Json { } } - instance Jsonable[Map[k, v]] with ToString[k], FromString[k], Order[k], ToJson[v], FromJson[v] - - instance ToJson[Option[a]] with ToJson[a] { - pub def toJson(x: Option[a]): JsonElement = match x { - case Some(y) => JsonArray(Vector#{Json.ToJson.toJson(y)}) - case None => JsonArray(Vector#{}) - } - } - instance FromJson[Option[a]] with FromJson[a] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, Option[a]] = match x { case JsonArray(v) if Vector.length(v) == 0 => Ok(None) @@ -300,17 +148,6 @@ mod Json { } } - instance Jsonable[Option[a]] with FromJson[a], ToJson[a] - - instance ToJson[(a, b)] with ToJson[a], ToJson[b] { - pub def toJson(x: (a, b)): JsonElement = { - let (a, b) = x; - let jsonA = Json.ToJson.toJson(a); - let jsonB = Json.ToJson.toJson(b); - JsonArray(Vector#{jsonA, jsonB}) - } - } - instance FromJson[(a, b)] with FromJson[a], FromJson[b] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, (a, b)] = match x { case JsonArray(v) if Vector.length(v) == 2 => @@ -325,18 +162,6 @@ mod Json { } } - instance Jsonable[(a, b)] with Jsonable[a], Jsonable[b] - - instance ToJson[(a, b, c)] with ToJson[a], ToJson[b], ToJson[c] { - pub def toJson(x: (a, b, c)): JsonElement = { - let (a, b, c) = x; - let jsonA = Json.ToJson.toJson(a); - let jsonB = Json.ToJson.toJson(b); - let jsonC = Json.ToJson.toJson(c); - JsonArray(Vector#{jsonA, jsonB, jsonC}) - } - } - instance FromJson[(a, b, c)] with FromJson[a], FromJson[b], FromJson[c] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, (a, b, c)] = match x { case JsonArray(v) if Vector.length(v) == 3 => @@ -352,19 +177,6 @@ mod Json { } } - instance Jsonable[(a, b, c)] with Jsonable[a], Jsonable[b], Jsonable[c] - - instance ToJson[(a, b, c, d)] with ToJson[a], ToJson[b], ToJson[c], ToJson[d] { - pub def toJson(x: (a, b, c, d)): JsonElement = { - let (a, b, c, d) = x; - let jsonA = Json.ToJson.toJson(a); - let jsonB = Json.ToJson.toJson(b); - let jsonC = Json.ToJson.toJson(c); - let jsonD = Json.ToJson.toJson(d); - JsonArray(Vector#{jsonA, jsonB, jsonC, jsonD}) - } - } - instance FromJson[(a, b, c, d)] with FromJson[a], FromJson[b], FromJson[c], FromJson[d] { pub def fromJsonAt(p: Path, x: JsonElement): Result[JsonError, (a, b, c, d)] = match x { case JsonArray(v) if Vector.length(v) == 4 => @@ -381,18 +193,10 @@ mod Json { } } - instance Jsonable[(a, b, c, d)] with Jsonable[a], Jsonable[b], Jsonable[c], Jsonable[d] - - instance ToJson[JsonElement] { - pub def toJson(x: JsonElement): JsonElement = x - } - instance FromJson[JsonElement] { pub def fromJsonAt(_: Path, x: JsonElement): Result[JsonError, JsonElement] = Ok(x) } - instance Jsonable[JsonElement] - /// /// Returns the value associated with the given key in the given map. /// diff --git a/src/Json/JsonElement.flix b/src/Json/JsonElement.flix new file mode 100644 index 0000000..4ace868 --- /dev/null +++ b/src/Json/JsonElement.flix @@ -0,0 +1,22 @@ +pub mod Json.JsonElement { + pub enum JsonElement { + case JsonObject(Map[String, JsonElement]) + case JsonArray(Vector[JsonElement]) + case JsonString(String) + case JsonNumber(BigDecimal) + case JsonBool(Bool) + case JsonNull + } + + instance Eq[JsonElement] { + pub def eq(x: JsonElement, y: JsonElement): Bool = match (x, y) { + case (JsonObject(a), JsonObject(b)) => a == b + case (JsonArray(a), JsonArray(b)) => a == b + case (JsonString(a), JsonString(b)) => a == b + case (JsonNumber(a), JsonNumber(b)) => a == b + case (JsonBool(a), JsonBool(b)) => a == b + case (JsonNull, JsonNull) => true + case _ => false + } + } +} \ No newline at end of file diff --git a/src/Json/JsonError.flix b/src/Json/JsonError.flix new file mode 100644 index 0000000..6d4ce6b --- /dev/null +++ b/src/Json/JsonError.flix @@ -0,0 +1,5 @@ +pub mod Json.JsonError { + use Json.Path; + + pub enum JsonError(Path, Set[String]) with Eq +} diff --git a/src/Json/Jsonable.flix b/src/Json/Jsonable.flix new file mode 100644 index 0000000..220fcae --- /dev/null +++ b/src/Json/Jsonable.flix @@ -0,0 +1,45 @@ +pub mod Json.Jsonable { + use Json.FromJson + use Json.JsonElement + use Json.ToJson + + pub lawful trait Jsonable[a] with ToJson[a], FromJson[a] { + law inverse: forall (x: a) with Eq[a] { + x |> Json.ToJson.toJson |> Json.FromJson.fromJson == Ok(x) + } + } + + instance Jsonable[Bool] + + instance Jsonable[Char] + + instance Jsonable[BigDecimal] + + instance Jsonable[Int8] + + instance Jsonable[Int16] + + instance Jsonable[Int32] + + instance Jsonable[Int64] + + instance Jsonable[BigInt] + + instance Jsonable[String] + + instance Jsonable[List[a]] with Jsonable[a] + + instance Jsonable[Vector[a]] with Jsonable[a] + + instance Jsonable[Map[k, v]] with ToString[k], FromString[k], Order[k], ToJson[v], FromJson[v] + + instance Jsonable[Option[a]] with FromJson[a], ToJson[a] + + instance Jsonable[(a, b)] with Jsonable[a], Jsonable[b] + + instance Jsonable[(a, b, c)] with Jsonable[a], Jsonable[b], Jsonable[c] + + instance Jsonable[(a, b, c, d)] with Jsonable[a], Jsonable[b], Jsonable[c], Jsonable[d] + + instance Jsonable[JsonElement] +} diff --git a/src/Json/ToJson.flix b/src/Json/ToJson.flix new file mode 100644 index 0000000..344444a --- /dev/null +++ b/src/Json/ToJson.flix @@ -0,0 +1,159 @@ +pub mod Json.ToJson { + use Json.JsonElement + use Json.JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool} + + pub trait ToJson[a] { + pub def toJson(x: a): JsonElement + } + + instance ToJson[Bool] { + pub def toJson(x: Bool): JsonElement = JsonBool(x) + } + + instance ToJson[Char] { + pub def toJson(x: Char): JsonElement = JsonString(Char.toString(x)) + } + + instance ToJson[Float32] { + pub def toJson(x: Float32): JsonElement = { + match Float32.tryToBigDecimal(x) { + case Some(num) => JsonNumber(num) + case None => + if (Float32.isInfinite(x)) { + if (x > 0.0f32) { + JsonString("Infinity") + } else { + JsonString("-Infinity") + } + } else if (Float32.isNan(x)) { + JsonString("NaN") + } else { + bug!("invalid Float32: ${x}") + } + } + } + } + + instance ToJson[Float64] { + pub def toJson(x: Float64): JsonElement = { + match Float64.tryToBigDecimal(x) { + case Some(num) => JsonNumber(num) + case None => + if (Float64.isInfinite(x)) { + if (x > 0.0f64) { + JsonString("Infinity") + } else { + JsonString("-Infinity") + } + } else if (Float64.isNan(x)) { + JsonString("NaN") + } else { + bug!("invalid Float64: ${x}") + } + } + } + } + + instance ToJson[BigDecimal] { + pub def toJson(x: BigDecimal): JsonElement = JsonNumber(x) + } + + instance ToJson[Int8] { + pub def toJson(x: Int8): JsonElement = { + JsonNumber(Int8.toBigDecimal(x)) + } + } + + instance ToJson[Int16] { + pub def toJson(x: Int16): JsonElement = { + JsonNumber(Int16.toBigDecimal(x)) + } + } + + instance ToJson[Int32] { + pub def toJson(x: Int32): JsonElement = { + JsonNumber(Int32.toBigDecimal(x)) + } + } + + instance ToJson[Int64] { + pub def toJson(x: Int64): JsonElement = { + JsonNumber(Int64.toBigDecimal(x)) + } + } + + instance ToJson[BigInt] { + pub def toJson(x: BigInt): JsonElement = { + JsonNumber(BigInt.toBigDecimal(x)) + } + } + + instance ToJson[String] { + pub def toJson(x: String): JsonElement = JsonString(x) + } + + instance ToJson[List[a]] with ToJson[a] { + pub def toJson(x: List[a]): JsonElement = { + let elems = List.map(Json.ToJson.toJson, x) |> List.toVector; + JsonArray(elems) + } + } + + instance ToJson[Vector[a]] with ToJson[a] { + pub def toJson(x: Vector[a]): JsonElement = { + let elems = Vector.map(Json.ToJson.toJson, x); + JsonArray(elems) + } + } + + instance ToJson[Map[k, v]] with ToString[k], ToJson[v] { + pub def toJson(x: Map[k, v]): JsonElement = { + let map = x + |> Map.toList + |> List.map(match (k, v) -> (ToString.toString(k), Json.ToJson.toJson(v))) + |> List.toMap; + JsonObject(map) + } + } + + instance ToJson[Option[a]] with ToJson[a] { + pub def toJson(x: Option[a]): JsonElement = match x { + case Some(y) => JsonArray(Vector#{Json.ToJson.toJson(y)}) + case None => JsonArray(Vector#{}) + } + } + + instance ToJson[(a, b)] with ToJson[a], ToJson[b] { + pub def toJson(x: (a, b)): JsonElement = { + let (a, b) = x; + let jsonA = Json.ToJson.toJson(a); + let jsonB = Json.ToJson.toJson(b); + JsonArray(Vector#{jsonA, jsonB}) + } + } + + instance ToJson[(a, b, c)] with ToJson[a], ToJson[b], ToJson[c] { + pub def toJson(x: (a, b, c)): JsonElement = { + let (a, b, c) = x; + let jsonA = Json.ToJson.toJson(a); + let jsonB = Json.ToJson.toJson(b); + let jsonC = Json.ToJson.toJson(c); + JsonArray(Vector#{jsonA, jsonB, jsonC}) + } + } + + instance ToJson[(a, b, c, d)] with ToJson[a], ToJson[b], ToJson[c], ToJson[d] { + pub def toJson(x: (a, b, c, d)): JsonElement = { + let (a, b, c, d) = x; + let jsonA = Json.ToJson.toJson(a); + let jsonB = Json.ToJson.toJson(b); + let jsonC = Json.ToJson.toJson(c); + let jsonD = Json.ToJson.toJson(d); + JsonArray(Vector#{jsonA, jsonB, jsonC, jsonD}) + } + } + + instance ToJson[JsonElement] { + pub def toJson(x: JsonElement): JsonElement = x + } +} diff --git a/src/Parse.flix b/src/Parse.flix index 1520080..08e7ef9 100644 --- a/src/Parse.flix +++ b/src/Parse.flix @@ -1,6 +1,6 @@ mod Json.Parse { + use Json.JsonElement use Json.JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool, JsonNull} - use Json.JsonElement; use Option.flatMap; // Each main rule must start with a key character and consume trailing whitespace diff --git a/src/Path.flix b/src/Path.flix index 1e724b0..e686b19 100644 --- a/src/Path.flix +++ b/src/Path.flix @@ -1,7 +1,7 @@ mod Json.Path { + use Json.JsonElement use Json.JsonElement.JsonArray; use Json.JsonElement.JsonObject; - use Json.JsonElement; use Json.Utils.escape; use Path.{Root, AtIndex, AtKey} diff --git a/test/TestJsonable.flix b/test/TestJsonable.flix index 8d3b055..772ad54 100644 --- a/test/TestJsonable.flix +++ b/test/TestJsonable.flix @@ -1,5 +1,10 @@ mod TestJsonable { use Json.FromJson + use Json.FromJson.fromJson + use Json.FromJson.fromJsonAt + use Json.FromJson.fromNullableJson + use Json.FromJson.getAtKey + use Json.FromJson.getAtKeyOpt use Json.JsonElement use Json.JsonElement.JsonNull use Json.JsonElement.JsonObject @@ -7,13 +12,8 @@ mod TestJsonable { use Json.JsonError use Json.Jsonable use Json.ToJson - use Json.getAtKey; - use Json.getAtKeyOpt; - use Json.FromJson.fromJson - use Json.FromJson.fromJsonAt - use Json.FromJson.fromNullableJson - use Json.Path.Path use Json.ToJson.toJson + use Json.Path @Test pub def testBool01(): Unit \ Assert = testRoundTrip(true) diff --git a/test/TestPath.flix b/test/TestPath.flix index 1144871..abf13bc 100644 --- a/test/TestPath.flix +++ b/test/TestPath.flix @@ -1,7 +1,7 @@ mod TestPath { use Json.Path.apply; use Json.Path.{!!}; - use Json.Path.Path.Root; + use Json.Path.Root; use Json.JsonElement.{JsonObject, JsonArray, JsonString, JsonNumber, JsonBool, JsonNull} @Test diff --git a/test/TestReadme.flix b/test/TestReadme.flix index 7e878cc..a3d64fa 100644 --- a/test/TestReadme.flix +++ b/test/TestReadme.flix @@ -2,11 +2,11 @@ mod TestReadme { use Json.ToJson use Json.FromJson; use Json.FromJson.fromJsonAt; - use Json.Path.Path; + use Json.Path; use Json.JsonElement; use Json.JsonError; - use Json.getAtKey; - use Json.getAtKeyOpt; + use Json.FromJson.getAtKey; + use Json.FromJson.getAtKeyOpt; use Json.Path.{!!}; enum Person({