diff --git a/ppx_src/bin/Codecs.re b/ppx_src/bin/Codecs.re index 688c07b..e14b20d 100644 --- a/ppx_src/bin/Codecs.re +++ b/ppx_src/bin/Codecs.re @@ -75,6 +75,10 @@ and generateConstrCodecs = ({ doEncode, doDecode }, { Location.txt: identifier, doEncode ? Some([%expr Decco.resultToJson]) : None, doDecode ? Some([%expr Decco.resultFromJson]) : None ) + | Ldot(Ldot(Lident("Js"), "Dict"), "t") => ( + doEncode ? Some([%expr Decco.dictToJson]) : None, + doDecode ? Some([%expr Decco.dictFromJson]) : None + ) | Ldot(Ldot(Lident("Js"), "Json"), "t") => ( doEncode ? Some([%expr (v) => v]) : None, doDecode ? Some([%expr (v) => Belt.Result.Ok(v)]) : None, diff --git a/src/Decco.re b/src/Decco.re index 39b1647..d6fb65c 100644 --- a/src/Decco.re +++ b/src/Decco.re @@ -144,6 +144,28 @@ let resultFromJson = (okDecoder, errorDecoder, json) => | None => error("Not an array", json) }; +let dictToJson = (encoder, dict) => + dict->Js.Dict.map((. a) => encoder(a), _)->Js.Json.object_; + +let dictFromJson = (decoder, json) => + switch (Js.Json.decodeObject(json)) { + | Some(dict) => + dict + ->Js.Dict.entries + ->Belt.Array.reduce(Ok(Js.Dict.empty()), (acc, (key, value)) => + switch (acc, decoder(value)) { + | (Error(_), _) => acc + + | (_, Error({ path } as error)) => Error({...error, path: "." ++ key ++ path}) + + | (Ok(prev), Ok(newVal)) => + let () = prev->Js.Dict.set(key, newVal); + Ok(prev); + } + ); + | None => Error({path: "", message: "Not a dict", value: json}) +}; + module Codecs { include Decco_Codecs; let string = (stringToJson, stringFromJson); diff --git a/test/__tests__/test.re b/test/__tests__/test.re index c0a2003..bdbf33a 100644 --- a/test/__tests__/test.re +++ b/test/__tests__/test.re @@ -16,9 +16,11 @@ open Belt.Result; */ [@decco] type l('a) = list('a); [@decco] type o('a) = option('a); [@decco] type r('v, 'e) = Belt.Result.t('v, 'e); +[@decco] type d('v) = Js.Dict.t('v); [@decco] type simpleVar('a) = 'a; [@decco] type j = Js.Json.t; [@decco] type optionList = l(o(s)); +[@decco] type dictInt = d(int); [@decco] type variant = A | B(i) | C(i, s); [@decco] type record = { hey: s, @@ -508,6 +510,27 @@ describe("optionList", () => { }); }); +describe("dictInt", () => { + testEncode( + "dictInt_encode", Js.Dict.fromArray([|("foo", 1), ("bar", 2)|]), + dictInt_encode, {|{"foo":1,"bar":2}|} + ); + + describe("dictInt_decode", () => { + let json = {|{"foo":1,"bar":2}|} |> Js.Json.parseExn; + testGoodDecode("good", dictInt_decode, json, Js.Dict.fromArray([|("foo", 1), ("bar", 2)|])); + + describe("bad", () => { + let badDict = {|{"foo":1,"bar":"baz"}|} |> Js.Json.parseExn; + testBadDecode("mixed types", dictInt_decode, badDict, { + path: ".bar", + message: "Not a number", + value: Js.Json.string("baz") + }); + }); + }); +}); + describe("Js.Json.t", () => { test("j_encode", () => { let v = Js.Json.string("jay");