From 2202a62604d2a1641895537e67e84c73b9fafe89 Mon Sep 17 00:00:00 2001 From: Ivan Yuriev Date: Sun, 8 Dec 2024 01:39:20 +0300 Subject: [PATCH] =?UTF-8?q?implemented=20json=20serde=20=F0=9F=A6=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gleam.toml | 3 + manifest.toml | 7 +++ src/json_serde.gleam | 119 ++++++++++++++++++++++++++++++++++++++++ src/revault.gleam | 5 +- test/revault_test.gleam | 15 +++++ 5 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 src/json_serde.gleam diff --git a/gleam.toml b/gleam.toml index 8f26628..9e12436 100644 --- a/gleam.toml +++ b/gleam.toml @@ -15,6 +15,9 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.45.0 and < 2.0.0" gleam_otp = ">= 0.14.1 and < 1.0.0" +gleam_json = ">= 2.1.0 and < 3.0.0" +simplifile = ">= 2.2.0 and < 3.0.0" +decode = ">= 0.5.0 and < 1.0.0" [dev-dependencies] gleeunit = ">= 1.0.0 and < 2.0.0" diff --git a/manifest.toml b/manifest.toml index f162689..a43da85 100644 --- a/manifest.toml +++ b/manifest.toml @@ -3,15 +3,22 @@ packages = [ { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, + { name = "decode", version = "0.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "decode", source = "hex", outer_checksum = "05E14DC95A550BA51B8774485B04894B87A898C588B9B1C920104B110AED218B" }, + { name = "filepath", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "67A6D15FB39EEB69DD31F8C145BB5A421790581BD6AA14B33D64D5A55DBD6587" }, { name = "gleam_erlang", version = "0.32.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "B18643083A0117AC5CFD0C1AEEBE5469071895ECFA426DCC26517A07F6AD9948" }, + { name = "gleam_json", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "0A57FB5666E695FD2BEE74C0428A98B0FC11A395D2C7B4CDF5E22C5DD32C74C6" }, { name = "gleam_otp", version = "0.14.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "5A8CE8DBD01C29403390A7BD5C0A63D26F865C83173CF9708E6E827E53159C65" }, { name = "gleam_stdlib", version = "0.45.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "206FCE1A76974AECFC55AEBCD0217D59EDE4E408C016E2CFCCC8FF51278F186E" }, { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, { name = "ranger", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "B8F3AFF23A3A5B5D9526B8D18E7C43A7DFD3902B151B97EC65397FE29192B695" }, + { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, ] [requirements] birl = { version = ">= 1.7.1 and < 2.0.0" } +decode = { version = ">= 0.5.0 and < 1.0.0" } +gleam_json = { version = ">= 2.1.0 and < 3.0.0" } gleam_otp = { version = ">= 0.14.1 and < 1.0.0" } gleam_stdlib = { version = ">= 0.45.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } +simplifile = { version = ">= 2.2.0 and < 3.0.0" } diff --git a/src/json_serde.gleam b/src/json_serde.gleam new file mode 100644 index 0000000..8b6e205 --- /dev/null +++ b/src/json_serde.gleam @@ -0,0 +1,119 @@ +//// Functions to allow serialization from Tree to Json and back! + +import decode/zero +import gleam/dict +import gleam/dynamic + +import gleam/json.{bool, float, int, null, object, string} +import gleam/list + +import gleam/result + +import tree + +pub type JsonCodecError { + EncodingError + DecodingError +} + +pub fn serialize(tree) { + tree |> tree_to_json |> json.to_string +} + +fn tree_to_json(tree: tree.Tree) -> json.Json { + case tree { + tree.Leaf(_) as lf -> leaf_to_json(lf) + tree.Node(children) | tree.Root(children) -> { + children + |> dict.to_list + |> list.map(fn(pair) { + let #(k, v) = pair + #(k, v |> tree_to_json) + }) + |> object() + } + } +} + +fn leaf_to_json(tree: tree.Tree) -> json.Json { + case tree { + tree.Leaf(data) -> + case data { + tree.Bool(b) -> bool(b) + tree.Float(f) -> float(f) + tree.Int(i) -> int(i) + tree.Null -> null() + tree.String(s) -> string(s) + } + _ -> panic as "Tried to serialize not a Leaf." + } +} + +pub fn deserialize(string) { + case json.decode(string, zero.run(_, zero.dynamic)) { + Error(_) -> Error(DecodingError) + Ok(data) -> data |> tree_decoder(True) |> Ok + } +} + +fn tree_decoder(data, root) { + let _ = case dynamic.classify(data), root { + "Dict", True -> { + let children = + zero.dict(zero.string, zero.dynamic) + |> zero.run(data, _) + + case children { + Error(_) -> panic as "EncodingError" + Ok(children) -> + children + |> dict.map_values(fn(_, v) { tree_decoder(v, False) }) + |> tree.Root + } + } + + "Dict", False -> { + let children = + zero.dict(zero.string, zero.dynamic) + |> zero.run(data, _) + + case children { + Error(_) -> panic as "EncodingError" + Ok(children) -> + children + |> dict.map_values(fn(_, v) { tree_decoder(v, False) }) + } + |> tree.Node + } + _, _ -> + case leaf_decoder(data) { + Error(_) -> panic as "EncodingError" + Ok(leaf) -> leaf + } + } +} + +fn leaf_decoder(data) { + let decoded = case dynamic.classify(data) { + "Int" -> { + use value <- result.try(dynamic.int(data)) + Ok(tree.Int(value)) + } + "Float" -> { + use value <- result.try(dynamic.float(data)) + Ok(tree.Float(value)) + } + "Bool" -> { + use value <- result.try(dynamic.bool(data)) + Ok(tree.Bool(value)) + } + "String" -> { + use value <- result.try(dynamic.string(data)) + Ok(tree.String(value)) + } + _ -> Ok(tree.Null) + } + + use data <- result.try(decoded) + Ok(tree.Leaf(data)) +} diff --git a/src/revault.gleam b/src/revault.gleam index 5086145..0067e8c 100644 --- a/src/revault.gleam +++ b/src/revault.gleam @@ -1,6 +1,3 @@ -import gleam/io -import gleam/string - pub fn main() { - ".a.b.c" |> string.split(".") |> io.debug + todo } diff --git a/test/revault_test.gleam b/test/revault_test.gleam index a51cf5b..14e3662 100644 --- a/test/revault_test.gleam +++ b/test/revault_test.gleam @@ -1,6 +1,7 @@ import gleam/dict import gleeunit import gleeunit/should +import json_serde import query import tree @@ -51,3 +52,17 @@ pub fn tree_test() { query.get(tree, ".a2.b3", False) |> should.equal(tree.Leaf(tree.Int(43)) |> Ok()) } + +pub fn json_test() { + let tree = tree.new() + let assert Ok(tree) = query.set(tree, ".a.b.c.d", tree.String("foo")) + let assert Ok(tree) = query.set(tree, ".a.b2.c", tree.String("bar")) + let assert Ok(tree) = query.set(tree, ".a.b", tree.String("new foo")) + let assert Ok(tree) = query.set(tree, ".a2.b3", tree.Int(42)) + let assert Ok(tree) = query.set(tree, ".a2.b3", tree.Int(43)) + + let json = tree |> json_serde.serialize + let assert Ok(decoded) = json |> json_serde.deserialize() + + should.equal(tree, decoded) +}