diff --git a/Makefile b/Makefile index 2f60cdc..8411bfc 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ help: ## Print this help message .PHONY: create-switch create-switch: ## Create opam switch - opam switch create . 4.14.1 -y --deps-only + opam switch create . 5.1.0~rc3 -y --deps-only .PHONY: init init: create-switch install ## Configure everything to develop this repository in local @@ -23,8 +23,7 @@ install: ## Install development dependencies yarn opam update opam install -y . --deps-only --with-test - # used to build the tests - opam pin add melange-jest.dev -y git+https://github.com/melange-community/melange-jest.git#main + opam-check-npm-deps .PHONY: build build: ## Build the project diff --git a/examples/decode.ml b/examples/decode.ml index 12b5087..307b9af 100644 --- a/examples/decode.ml +++ b/examples/decode.ml @@ -1,29 +1,28 @@ (* Decoding a fixed JSON data structure using Json.Decode *) -let mapJsonObjectString f decoder (encoder: int -> Js.Json.t) str = +let mapJsonObjectString f decoder (encoder : int -> Js.Json.t) str = let json = Json.parseOrRaise str in Json.Decode.(dict decoder json) - |> Js.Dict.map ((fun v -> f v) [@bs]) - |> Json.Encode.dict encoder - |> Json.stringify + |> Js.Dict.map (fun [@u] v -> f v) + |> Json.Encode.dict encoder |> Json.stringify -let sum = - Array.fold_left (+) 0 +let sum = Array.fold_left ( + ) 0 (* prints `{ "foo": 6, "bar": 24 }` *) let _ = - Js.log @@ - (mapJsonObjectString sum Json.Decode.(array int) Json.Encode.int {| + Js.log + @@ mapJsonObjectString sum + Json.Decode.(array int) + Json.Encode.int + {| { "foo": [1, 2, 3], "bar": [9, 8, 7] } - |}) + |} (* Error handling *) let _ = let json = {|{ "y": 42 } |} |> Json.parseOrRaise in match Json.Decode.(field "x" int json) with - | x -> - Js.log x - | exception Json.Decode.DecodeError msg -> - Js.log ("Error:" ^ msg) \ No newline at end of file + | x -> Js.log x + | exception Json.Decode.DecodeError msg -> Js.log ("Error:" ^ msg) diff --git a/melange-json.opam b/melange-json.opam index eeaba36..0cb8e2e 100644 --- a/melange-json.opam +++ b/melange-json.opam @@ -7,16 +7,17 @@ maintainer: [ authors: [ "glennsl" ] -license: "(LGPL-3.0 OR MPL-2.0)" +license: ["LGPL-3.0-only" "MPL-2.0"] homepage: "https://github.com/melange-community/melange-json/" doc: "https://github.com/melange-community/melange-json/" bug-reports: "https://github.com/melange-community/melange-json/issues" depends: [ "dune" {>= "3.8"} - "ocaml" {>= "4.14.0"} - "melange" {>= "1.0.0"} + "ocaml" + "melange" {>= "2.0.0"} "melange-jest" {with-test} "reason" {with-test} + "opam-check-npm-deps" {with-test} # todo: use with-dev-setup once opam 2.2 is out "ocaml-lsp-server" {with-test} "odoc" {with-doc} ] @@ -35,3 +36,10 @@ build: [ ] ] dev-repo: "git+https://github.com/melange-community/melange-json.git" +pin-depends: [ + [ "melange.2.0.0" "git+https://github.com/melange-re/melange.git#e114ad55d185badeb32b3c766c9ab547495eac1b" ] + [ "reason.3.10.0" "git+https://github.com/reasonml/reason.git#972261dab3b651ff8ab9b8b9fcc32940595073dc" ] + [ "melange-jest.dev" "git+https://github.com/melange-community/melange-jest.git#acb6ef50beef3c486805d616b90aa7b56b51172d" ] + [ "melange-webapi.dev" "git+https://github.com/melange-community/melange-webapi.git#96dc1e2a867624d18050fad25cf1c71af7a098e1" ] + [ "melange-fetch.dev" "git+https://github.com/melange-community/melange-fetch.git#796f941b6b85eb7e6182ac6e4f40708bfde7a9a9" ] +] diff --git a/src/Json.ml b/src/Json.ml index f2f8ccd..c1dea7c 100644 --- a/src/Json.ml +++ b/src/Json.ml @@ -3,17 +3,14 @@ module Encode = Json_encode exception ParseError of string -let parse s = - try Some (Js.Json.parseExn s) with - | _ -> None +let parse s = try Some (Js.Json.parseExn s) with _ -> None let parseOrRaise s = - try Js.Json.parseExn s with - | Js.Exn.Error e -> + try Js.Json.parseExn s + with Js.Exn.Error e -> let message = - match Js.Exn.message e with - | Some m -> m - | None -> "Unknown error" - in raise @@ ParseError message + match Js.Exn.message e with Some m -> m | None -> "Unknown error" + in + raise @@ ParseError message -external stringify : Js.Json.t -> string = "JSON.stringify" [@@bs.val] \ No newline at end of file +external stringify : Js.Json.t -> string = "JSON.stringify" diff --git a/src/Json_decode.ml b/src/Json_decode.ml index 5168254..d6718c3 100644 --- a/src/Json_decode.ml +++ b/src/Json_decode.ml @@ -1,6 +1,5 @@ -external _unsafeCreateUninitializedArray : int -> 'a array = "Array" [@@bs.new] - -external _stringify : Js.Json.t -> string = "JSON.stringify" [@@bs.val] +external _unsafeCreateUninitializedArray : int -> 'a array = "Array" [@@mel.new] +external _stringify : Js.Json.t -> string = "JSON.stringify" let _isInteger value = Js.Float.isFinite value && Js.Math.floor_float value == value @@ -11,207 +10,175 @@ exception DecodeError of string let id json = json -let bool json = - if Js.typeof json = "boolean" then - (Obj.magic (json : Js.Json.t) : bool) - else - raise @@ DecodeError ("Expected boolean, got " ^ _stringify json) +let bool json = + if Js.typeof json = "boolean" then (Obj.magic (json : Js.Json.t) : bool) + else raise @@ DecodeError ("Expected boolean, got " ^ _stringify json) -let float json = - if Js.typeof json = "number" then - (Obj.magic (json : Js.Json.t) : float) - else - raise @@ DecodeError ("Expected number, got " ^ _stringify json) +let float json = + if Js.typeof json = "number" then (Obj.magic (json : Js.Json.t) : float) + else raise @@ DecodeError ("Expected number, got " ^ _stringify json) -let int json = +let int json = let f = float json in - if _isInteger f then - (Obj.magic (f : float) : int) - else - raise @@ DecodeError ("Expected integer, got " ^ _stringify json) + if _isInteger f then (Obj.magic (f : float) : int) + else raise @@ DecodeError ("Expected integer, got " ^ _stringify json) -let string json = - if Js.typeof json = "string" then - (Obj.magic (json : Js.Json.t) : string) - else - raise @@ DecodeError ("Expected string, got " ^ _stringify json) +let string json = + if Js.typeof json = "string" then (Obj.magic (json : Js.Json.t) : string) + else raise @@ DecodeError ("Expected string, got " ^ _stringify json) let char json = let s = string json in - if String.length s = 1 then - String.get s 0 + if String.length s = 1 then String.get s 0 else - raise @@ DecodeError ("Expected single-character string, got " ^ _stringify json) + raise + @@ DecodeError ("Expected single-character string, got " ^ _stringify json) -let date json = - json |> string - |> Js.Date.fromString +let date json = json |> string |> Js.Date.fromString let nullable decode json = - if (Obj.magic json : 'a Js.null) == Js.null then - Js.null - else - Js.Null.return (decode json) + if (Obj.magic json : 'a Js.null) == Js.null then Js.null + else Js.Null.return (decode json) (* TODO: remove this? *) -let nullAs value json = - if (Obj.magic json : 'a Js.null) == Js.null then - value - else - raise @@ DecodeError ("Expected null, got " ^ _stringify json) - -let array decode json = - if Js.Array.isArray json then begin +let nullAs value json = + if (Obj.magic json : 'a Js.null) == Js.null then value + else raise @@ DecodeError ("Expected null, got " ^ _stringify json) + +let array decode json = + if Js.Array.isArray json then ( let source = (Obj.magic (json : Js.Json.t) : Js.Json.t array) in let length = Js.Array.length source in let target = _unsafeCreateUninitializedArray length in for i = 0 to length - 1 do - let value = - try - decode (Array.unsafe_get source i) - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin array at index " ^ string_of_int i) - in - Array.unsafe_set target i value; + let value = + try decode (Array.unsafe_get source i) + with DecodeError msg -> + raise @@ DecodeError (msg ^ "\n\tin array at index " ^ string_of_int i) + in + Array.unsafe_set target i value done; - target - end - else - raise @@ DecodeError ("Expected array, got " ^ _stringify json) + target) + else raise @@ DecodeError ("Expected array, got " ^ _stringify json) -let list decode json = - json |> array decode |> Array.to_list +let list decode json = json |> array decode |> Array.to_list let pair decodeA decodeB json = - if Js.Array.isArray json then begin + if Js.Array.isArray json then let source = (Obj.magic (json : Js.Json.t) : Js.Json.t array) in let length = Js.Array.length source in if length = 2 then try - decodeA (Array.unsafe_get source 0), - decodeB (Array.unsafe_get source 1) - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin pair/tuple2") + ( decodeA (Array.unsafe_get source 0), + decodeB (Array.unsafe_get source 1) ) + with DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin pair/tuple2") else - raise @@ DecodeError ({j|Expected array of length 2, got array of length $length|j}) - end - else - raise @@ DecodeError ("Expected array, got " ^ _stringify json) + raise + @@ DecodeError + {j|Expected array of length 2, got array of length $length|j} + else raise @@ DecodeError ("Expected array, got " ^ _stringify json) let tuple2 = pair let tuple3 decodeA decodeB decodeC json = - if Js.Array.isArray json then begin + if Js.Array.isArray json then let source = (Obj.magic (json : Js.Json.t) : Js.Json.t array) in let length = Js.Array.length source in if length = 3 then try - decodeA (Array.unsafe_get source 0), - decodeB (Array.unsafe_get source 1), - decodeC (Array.unsafe_get source 2) - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin tuple3") + ( decodeA (Array.unsafe_get source 0), + decodeB (Array.unsafe_get source 1), + decodeC (Array.unsafe_get source 2) ) + with DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin tuple3") else - raise @@ DecodeError ({j|Expected array of length 3, got array of length $length|j}) - end - else - raise @@ DecodeError ("Expected array, got " ^ _stringify json) + raise + @@ DecodeError + {j|Expected array of length 3, got array of length $length|j} + else raise @@ DecodeError ("Expected array, got " ^ _stringify json) let tuple4 decodeA decodeB decodeC decodeD json = - if Js.Array.isArray json then begin + if Js.Array.isArray json then let source = (Obj.magic (json : Js.Json.t) : Js.Json.t array) in let length = Js.Array.length source in if length = 4 then try - decodeA (Array.unsafe_get source 0), - decodeB (Array.unsafe_get source 1), - decodeC (Array.unsafe_get source 2), - decodeD (Array.unsafe_get source 3) - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin tuple4") + ( decodeA (Array.unsafe_get source 0), + decodeB (Array.unsafe_get source 1), + decodeC (Array.unsafe_get source 2), + decodeD (Array.unsafe_get source 3) ) + with DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin tuple4") else - raise @@ DecodeError ({j|Expected array of length 4, got array of length $length|j}) - end - else - raise @@ DecodeError ("Expected array, got " ^ _stringify json) - -let dict decode json = - if Js.typeof json = "object" && - not (Js.Array.isArray json) && - not ((Obj.magic json : 'a Js.null) == Js.null) - then begin + raise + @@ DecodeError + {j|Expected array of length 4, got array of length $length|j} + else raise @@ DecodeError ("Expected array, got " ^ _stringify json) + +let dict decode json = + if + Js.typeof json = "object" + && (not (Js.Array.isArray json)) + && not ((Obj.magic json : 'a Js.null) == Js.null) + then ( let source = (Obj.magic (json : Js.Json.t) : Js.Json.t Js.Dict.t) in let keys = Js.Dict.keys source in let l = Js.Array.length keys in let target = Js.Dict.empty () in for i = 0 to l - 1 do - let key = (Array.unsafe_get keys i) in - let value = - try - decode (Js.Dict.unsafeGet source key) - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin dict") - in - Js.Dict.set target key value; + let key = Array.unsafe_get keys i in + let value = + try decode (Js.Dict.unsafeGet source key) + with DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tin dict") + in + Js.Dict.set target key value done; - target - end - else - raise @@ DecodeError ("Expected object, got " ^ _stringify json) + target) + else raise @@ DecodeError ("Expected object, got " ^ _stringify json) let field key decode json = - if - Js.typeof json = "object" && - not (Js.Array.isArray json) && - not ((Obj.magic json : 'a Js.null) == Js.null) - then begin - let dict = - (Obj.magic (json : Js.Json.t) : Js.Json.t Js.Dict.t) in + if + Js.typeof json = "object" + && (not (Js.Array.isArray json)) + && not ((Obj.magic json : 'a Js.null) == Js.null) + then + let dict = (Obj.magic (json : Js.Json.t) : Js.Json.t Js.Dict.t) in match Js.Dict.get dict key with - | Some value -> begin - try - decode value - with - DecodeError msg -> raise @@ DecodeError (msg ^ "\n\tat field '" ^ key ^ "'") - end - | None -> - raise @@ DecodeError ({j|Expected field '$(key)'|j}) - end - else - raise @@ DecodeError ("Expected object, got " ^ _stringify json) + | Some value -> ( + try decode value + with DecodeError msg -> + raise @@ DecodeError (msg ^ "\n\tat field '" ^ key ^ "'")) + | None -> raise @@ DecodeError {j|Expected field '$(key)'|j} + else raise @@ DecodeError ("Expected object, got " ^ _stringify json) let rec at key_path decoder = - match key_path with - | [key] -> field key decoder - | first::rest -> field first (at rest decoder) - | [] -> raise @@ Invalid_argument ("Expected key_path to contain at least one element") + match key_path with + | [ key ] -> field key decoder + | first :: rest -> field first (at rest decoder) + | [] -> + raise + @@ Invalid_argument "Expected key_path to contain at least one element" -let optional decode json = - try Some (decode json) with - | DecodeError _ -> None +let optional decode json = try Some (decode json) with DecodeError _ -> None let oneOf decoders json = let rec inner decoders errors = match decoders with | [] -> - let formattedErrors = "\n- " ^ Js.Array.joinWith "\n- " (Array.of_list (List.rev errors)) in - raise @@ DecodeError - ({j|All decoders given to oneOf failed. Here are all the errors: $formattedErrors\nAnd the JSON being decoded: |j} ^ _stringify json) - | decode::rest -> - try decode json with - | DecodeError e -> - inner rest (e :: errors) in + let formattedErrors = + "\n- " ^ Js.Array.joinWith "\n- " (Array.of_list (List.rev errors)) + in + raise + @@ DecodeError + ({j|All decoders given to oneOf failed. Here are all the errors: $formattedErrors\nAnd the JSON being decoded: |j} + ^ _stringify json) + | decode :: rest -> ( + try decode json with DecodeError e -> inner rest (e :: errors)) + in inner decoders [] -let either a b = - oneOf [a;b] +let either a b = oneOf [ a; b ] let withDefault default decode json = - try decode json with - | DecodeError _ -> default - -let map f decode json = - f (decode json) + try decode json with DecodeError _ -> default -let andThen b a json= - b (a json) json +let map f decode json = f (decode json) +let andThen b a json = b (a json) json diff --git a/src/Json_encode.ml b/src/Json_encode.ml index 0d19b64..a91c3ff 100644 --- a/src/Json_encode.ml +++ b/src/Json_encode.ml @@ -1,61 +1,49 @@ type 'a encoder = 'a -> Js.Json.t -external null : Js.Json.t = "null" [@@bs.val] +external null : Js.Json.t = "null" external string : string -> Js.Json.t = "%identity" external float : float -> Js.Json.t = "%identity" external int : int -> Js.Json.t = "%identity" external bool : bool -> Js.Json.t = "%identity" -let char c = - c |> String.make 1 - |> string - -let date d = - d |> Js.Date.toJSONUnsafe - |> string - -let nullable encode = function - | None -> null - | Some v -> encode v - -let withDefault d encode = function - | None -> d - | Some v -> encode v +let char c = c |> String.make 1 |> string +let date d = d |> Js.Date.toJSONUnsafe |> string +let nullable encode = function None -> null | Some v -> encode v +let withDefault d encode = function None -> d | Some v -> encode v external jsonDict : Js.Json.t Js.Dict.t -> Js.Json.t = "%identity" + let dict encode d = let pairs = Js.Dict.entries d in - let encodedPairs = Array.map (fun (k, v) -> (k, encode(v))) pairs in + let encodedPairs = Array.map (fun (k, v) -> (k, encode v)) pairs in jsonDict (Js.Dict.fromArray encodedPairs) -let object_ props: Js.Json.t = - props |> Js.Dict.fromList - |> jsonDict +let object_ props : Js.Json.t = props |> Js.Dict.fromList |> jsonDict external jsonArray : Js.Json.t array -> Js.Json.t = "%identity" -let array encode l = - l |> Array.map encode - |> jsonArray + +let array encode l = l |> Array.map encode |> jsonArray + let list encode = function | [] -> jsonArray [||] - | hd::tl as l -> + | hd :: tl as l -> let a = Array.make (List.length l) (encode hd) in let rec fill i = function | [] -> a - | hd::tl -> ( - Array.unsafe_set a i (encode hd); - fill (i + 1) tl - ) + | hd :: tl -> + Array.unsafe_set a i (encode hd); + fill (i + 1) tl in - jsonArray (fill 1 tl) + jsonArray (fill 1 tl) -let pair encodeA encodeB (a, b) = - jsonArray [|encodeA a; encodeB b|] +let pair encodeA encodeB (a, b) = jsonArray [| encodeA a; encodeB b |] let tuple2 = pair + let tuple3 encodeA encodeB encodeC (a, b, c) = - jsonArray [|encodeA a; encodeB b; encodeC c|] + jsonArray [| encodeA a; encodeB b; encodeC c |] + let tuple4 encodeA encodeB encodeC encodeD (a, b, c, d) = - jsonArray [|encodeA a; encodeB b; encodeC c; encodeD d|] + jsonArray [| encodeA a; encodeB b; encodeC c; encodeD d |] external stringArray : string array -> Js.Json.t = "%identity" external numberArray : float array -> Js.Json.t = "%identity" diff --git a/src/Json_encode.mli b/src/Json_encode.mli index de8b7be..a4ec3fb 100644 --- a/src/Json_encode.mli +++ b/src/Json_encode.mli @@ -3,7 +3,8 @@ type 'a encoder = 'a -> Js.Json.t (** The type of a encoder combinator *) -external null : Js.Json.t = "null" [@@bs.val] +external null : Js.Json.t = "null" + (** [null] is the singleton null JSON value *) external string : string -> Js.Json.t = "%identity" @@ -30,16 +31,22 @@ val nullable : 'a encoder -> 'a option -> Js.Json.t val withDefault : Js.Json.t -> 'a encoder -> 'a option -> Js.Json.t (** [withDefault default encoder option] returns the encoded value if present, oterwise [default] *) -val pair : 'a encoder -> 'b encoder -> ('a * 'b) -> Js.Json.t +val pair : 'a encoder -> 'b encoder -> 'a * 'b -> Js.Json.t (** [pair encoder encoder tuple] creates a JSON array from a tuple of size 2 *) -val tuple2 : 'a encoder -> 'b encoder -> ('a * 'b) -> Js.Json.t +val tuple2 : 'a encoder -> 'b encoder -> 'a * 'b -> Js.Json.t (** [tuple2 encoder encoder tuple] creates a JSON array from a tuple of size 2. Alias of [pair] *) -val tuple3 : 'a encoder -> 'b encoder -> 'c encoder -> ('a * 'b * 'c) -> Js.Json.t +val tuple3 : 'a encoder -> 'b encoder -> 'c encoder -> 'a * 'b * 'c -> Js.Json.t (** [tuple3 enc enc enc tuple] creates a JSON array from a tuple of size 3 *) -val tuple4 : 'a encoder -> 'b encoder -> 'c encoder -> 'd encoder -> ('a * 'b * 'c * 'd) -> Js.Json.t +val tuple4 : + 'a encoder -> + 'b encoder -> + 'c encoder -> + 'd encoder -> + 'a * 'b * 'c * 'd -> + Js.Json.t (** [tuple4 enc enc enc enc tuple] creates a JSON array from a tuple of size 4 *) external jsonDict : Js.Json.t Js.Dict.t -> Js.Json.t = "%identity"