aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLouis Pilfold <louis@lpil.uk>2024-12-19 12:48:12 +0000
committerLouis Pilfold <louis@lpil.uk>2024-12-22 10:56:21 +0000
commitf43a6366e44062a5928340599591048a89f7398f (patch)
tree60f81bfb2919b8a6d67da23f02dae09ae7eb3a0a
parent6dc7a93fba3f1fee0913efbe2889dc1e564f3ffc (diff)
downloadgleam_stdlib-f43a6366e44062a5928340599591048a89f7398f.tar.gz
gleam_stdlib-f43a6366e44062a5928340599591048a89f7398f.zip
Recursive decoder
-rw-r--r--src/gleam/dynamic/decode.gleam31
-rw-r--r--test/gleam/dynamic/decode_test.gleam26
2 files changed, 57 insertions, 0 deletions
diff --git a/src/gleam/dynamic/decode.gleam b/src/gleam/dynamic/decode.gleam
index 42755d5..bd68c50 100644
--- a/src/gleam/dynamic/decode.gleam
+++ b/src/gleam/dynamic/decode.gleam
@@ -981,3 +981,34 @@ pub fn new_primitive_decoder(
}
})
}
+
+/// Create a decoder that can refer to itself, useful for decoding for deeply
+/// nested data.
+///
+/// Attempting to create a recursive decoder without this function could result
+/// in an infinite loop. If you are using `field` or other `use`able function
+/// then you may not need to use this function.
+///
+/// ```gleam
+/// import gleam/dynamic
+/// import decode/zero.{type Decoder}
+///
+/// type Nested {
+/// Nested(List(Nested))
+/// Value(String)
+/// }
+///
+/// fn nested_decoder() -> Decoder(Nested) {
+/// use <- zero.recursive
+/// zero.one_of(zero.string |> zero.map(Value), [
+/// zero.list(nested_decoder()) |> zero.map(Nested),
+/// ])
+/// }
+/// ```
+///
+pub fn recursive(inner: fn() -> Decoder(a)) -> Decoder(a) {
+ Decoder(function: fn(data) {
+ let decoder = inner()
+ decoder.function(data)
+ })
+}
diff --git a/test/gleam/dynamic/decode_test.gleam b/test/gleam/dynamic/decode_test.gleam
index 582f2af..b176c70 100644
--- a/test/gleam/dynamic/decode_test.gleam
+++ b/test/gleam/dynamic/decode_test.gleam
@@ -927,3 +927,29 @@ pub fn js_map_test() {
|> should.be_ok
|> should.equal(dict.from_list([#("a", 10), #("b", 20), #("c", 30)]))
}
+
+type Nested {
+ Nested(List(Nested))
+ Value(String)
+}
+
+fn recursive_decoder() -> decode.Decoder(Nested) {
+ use <- decode.recursive()
+ decode.one_of(decode.string |> decode.map(Value), [
+ decode.list(recursive_decoder()) |> decode.map(Nested),
+ ])
+}
+
+pub fn recursive_test() {
+ let nested = [["one", "two"], ["three"], []]
+ let expected =
+ Nested([
+ Nested([Value("one"), Value("two")]),
+ Nested([Value("three")]),
+ Nested([]),
+ ])
+
+ decode.run(dynamic.from(nested), recursive_decoder())
+ |> should.be_ok
+ |> should.equal(expected)
+}