Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Unpublished

- **[breaking]** Library, PPX: Unify runtimes, replace `Json` module with
`Melange_json`, deprecate `Decode` and `Encode` modules.
([#36](https://github.com/melange-community/melange-json/pull/36))
- **[breaking]** PPX: Code to decode polyvariants doesn't use an additional
`_poly` function which was also generated by the PPX. Instead
`Unexpected_variant` error is used to signal that next decoder should be
Expand Down
48 changes: 23 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ Based on [@glennsl/bs-json](https://github.com/glennsl/bs-json).
The Decode module in particular provides a basic set of decoder functions to be
composed into more complex decoders. A decoder is a function that takes a
`Js.Json.t` and either returns a value of the desired type if successful or
raises a `DecodeError` exception if not. Other functions accept a decoder and
raises an `Of_json_error` exception if not. Other functions accept a decoder and
produce another decoder. Like `array`, which when given a decoder for type `t`
will return a decoder that tries to produce a value of type `t array`. So to
decode an `int array` you combine `Json.Decode.int` with `Json.Decode.array`
into `Json.Decode.(array int)`. An array of arrays of ints? `Json.Decode.(array
(array int))`. Dict containing arrays of ints? `Json.Decode.(dict (array int))`.
decode an `int array` you combine `Melange_json.Of_json.int` with `Melange_json.Of_json.array`
into `Melange_json.Of_json.(array int)`. An array of arrays of ints? `Melange_json.Of_json.(array
(array int))`. Dict containing arrays of ints? `Melange_json.Of_json.(dict (array int))`.

## Example

Expand All @@ -30,16 +30,16 @@ and point = {

module Decode = {
let point = json =>
Json.Decode.{
Melange_json.Of_json.{
x: json |> field("x", int),
y: json |> field("y", int)
};

let line = json =>
Json.Decode.{
Melange_json.Of_json.{
start: json |> field("start", point),
end_: json |> field("end", point),
thickness: json |> optional(field("thickness", int))
thickness: json |> try_or_none(field("thickness", int))
};
};

Expand All @@ -48,14 +48,14 @@ let data = {| {
"end": { "x": 5, "y": 8 }
} |};

let line = data |> Json.parseOrRaise
let line = data |> Melange_json.of_string
|> Decode.line;
```

NOTE: `Json.Decode.{ ... }` creates an ordinary record, but also opens the
`Json.Decode` module locally, within the scope delimited by the curly braces, so
NOTE: `Melange_json.Of_json.{ ... }` creates an ordinary record, but also opens the
`Melange_json.Of_json` module locally, within the scope delimited by the curly braces, so
we don't have to qualify the functions we use from it, like `field`, `int` and
`optional` here. You can also use `Json.Decode.( ... )` to open the module
`try_or_none` here. You can also use `Melange_json.Of_json.( ... )` to open the module
locally within the parentheses, if you're not creating a record.

See [examples](./examples/) for more.
Expand Down Expand Up @@ -86,13 +86,11 @@ Add `melange-json` to the `libraries` field in your `dune` file:

For the moment, please see the interface files:

* [Json](./src/Json.mli)
* [Json.Encode](./src/Json_encode.mli)
* [Json.Decode](./src/Json_decode.mli)
* [Melange_json](./src/melange_json.mli)

### Writing custom decoders and encoders

If you look at the type signature of `Json.Decode.array`, for example, you'll
If you look at the type signature of `Melange_json.Decode.array`, for example, you'll
see it takes an `'a decoder` and returns an `'a array decoder`. `'a decoder` is
just an alias for `Js.Json.t -> 'a`, so if we expand the type signature of
`array` we'll get `(Js.Json.t -> 'a) -> Js.Json.t -> 'a array`. We can now see
Expand All @@ -106,7 +104,7 @@ Let's look at `Decode.point` from the example above:

```reason
let point = json => {
open! Json.Decode;
open! Melange_json.Decode;
{
x: json |> field("x", int),
y: json |> field("y", int)
Expand All @@ -115,12 +113,12 @@ let point = json => {
```

This is a function `Js.Json.t -> point`, or a `point decoder`. So if we'd like
to decode an array of points, we can just pass it to `Json.Decode.array` to get
to decode an array of points, we can just pass it to `Melange_json.Of_json.array` to get
a `point array decoder` in return.

#### Builders

To write a decoder _builder_ like `Json.Decode.array` we need to take another
To write a decoder _builder_ like `Melange_json.Of_json.array` we need to take another
decoder as an argument, and thanks to currying we just need to apply it where
we'd otherwise use a fixed decoder. Say we want to be able to decode both `int
point`s and `float point`s. First we'd have to parameterize the type:
Expand All @@ -137,7 +135,7 @@ argument:

```reason
let point = (decodeNumber, json) => {
open! Json.Decode;
open! Melange_json.Decode;
{
x: json |> field("x", decodeNumber),
y: json |> field("y", decodeNumber)
Expand All @@ -148,8 +146,8 @@ let point = (decodeNumber, json) => {
And if we wish we can now create aliases for each variant:

```reason
let intPoint = point(Json.Decode.int);
let floatPoint = point(Json.Decode.float);
let intPoint = point(Melange_json.Of_json.int);
let floatPoint = point(Melange_json.Of_json.float);
```

#### Encoders
Expand Down Expand Up @@ -182,7 +180,7 @@ add the `[@@deriving json]` attribute to the type declaration,
ensuring the converters for primitives like `int` and `string` are in scope if necessary:

```ocaml
open Ppx_deriving_json_runtime.Primitives
open Melange_json.Primitives

type t = {
a: int;
Expand Down Expand Up @@ -217,7 +215,7 @@ type t = {
b: string [@json.default "-"];
} [@@deriving of_json]

let t = of_json (Json.parseOrRaise {|{"a": 42}|})
let t = of_json (Melange_json.of_string {|{"a": 42}|})
(* t = { a = 42; b = "-"; } *)
```

Expand All @@ -232,7 +230,7 @@ type t = {
b: string option [@json.option];
} [@@deriving of_json]

let t = of_json (Json.parseOrRaise {|{"a": 42}|})
let t = of_json (Melange_json.of_string {|{"a": 42}|})
(* t = { a = 42; b = None; } *)
```

Expand Down Expand Up @@ -263,7 +261,7 @@ type t = {
b: string [@json.key "B"];
} [@@deriving of_json]

let t = of_json (Json.parseOrRaise {|{"A": 42, "B": "foo"}|})
let t = of_json (Melange_json.of_string {|{"A": 42, "B": "foo"}|})
(* t = { a = 42; b = "foo"; } *)
```

Expand Down
38 changes: 14 additions & 24 deletions examples/complex.ml
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
type line = {
start: point;
end_: point;
thickness: int option
}
and point = {
x: int;
y: int
}
type line = { start : point; end_ : point; thickness : int option }
and point = { x : int; y : int }

module Decode = struct
module Of_json = struct
let point json =
Json.Decode.{
x = json |> field "x" int;
y = json |> field "y" int
}
Melange_json.Of_json.
{ x = json |> field "x" int; y = json |> field "y" int }

let line json =
Json.Decode.{
start = json |> field "start" point;
end_ = json |> field "end" point;
thickness = json |> optional (field "thickness" int)
}
Melange_json.Of_json.
{
start = json |> field "start" point;
end_ = json |> field "end" point;
thickness = json |> try_or_none (field "thickness" int);
}
end

let data = {| {
let data =
{| {
"start": { "x": 1, "y": -4 },
"end": { "x": 5, "y": 8 }
} |}

let _ =
data |> Json.parseOrRaise
|> Decode.line
|> Js.log
let _ = data |> Melange_json.of_string |> Of_json.line |> Js.log
24 changes: 13 additions & 11 deletions examples/decode.ml
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
(* Decoding a fixed JSON data structure using Json.Decode *)
(* Decoding a fixed JSON data structure using Melange_json.Of_json *)
let mapJsonObjectString f decoder (encoder : int -> Js.Json.t) str =
let json = Json.parseOrRaise str in
Json.Decode.(dict decoder json)
let json = Melange_json.of_string str in
Melange_json.Of_json.(js_dict decoder json)
|> Js.Dict.map ~f:(fun [@u] v -> f v)
|> Json.Encode.dict encoder |> Json.stringify
|> Melange_json.To_json.js_dict encoder
|> Melange_json.to_string

let sum = Array.fold_left ( + ) 0

(* prints `{ "foo": 6, "bar": 24 }` *)
let _ =
let () =
Js.log
@@ mapJsonObjectString sum
Json.Decode.(array int)
Json.Encode.int
Melange_json.Of_json.(array int)
Melange_json.To_json.int
{|
{
"foo": [1, 2, 3],
Expand All @@ -21,8 +22,9 @@ let _ =
|}

(* Error handling *)
let _ =
let json = {|{ "y": 42 } |} |> Json.parseOrRaise in
match Json.Decode.(field "x" int json) with
let () =
let json = {|{ "y": 42 } |} |> Melange_json.of_string in
match Melange_json.Of_json.(field "x" int json) with
| x -> Js.log x
| exception Json.Decode.DecodeError err -> Js.log ("Error:" ^ Json.Decode.error_to_string err)
| exception Melange_json.Of_json_error err ->
Js.log ("Error:" ^ Melange_json.of_json_error_to_string err)
3 changes: 2 additions & 1 deletion examples/dune
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
(target examples)
(alias examples)
(libraries melange-json)
(preprocess (pps melange.ppx)))
(preprocess
(pps melange.ppx)))
45 changes: 20 additions & 25 deletions examples/dynamicDict_Ocaml.ml
Original file line number Diff line number Diff line change
Expand Up @@ -13,47 +13,42 @@
Could be dynamic JS keys generated by user or generally at run time.
*)

type obj = {
static: string;
dynamics: int Js.Dict.t;
}
type obj = { static : string; dynamics : int Js.Dict.t }

module Decode = struct
module Of_json = struct
let obj json =
Json.Decode.{
static = json |> field "static" string;
dynamics = json |> field "dynamics" (dict int)
}
Melange_json.Of_json.
{
static = json |> field "static" string;
dynamics = json |> field "dynamics" (js_dict int);
}
end

module Encode = struct
module To_json = struct
let obj c =
Json.Encode.(
object_ [
"static", c.static |> string;
"dynamics", c.dynamics |> dict int
]
)
Melange_json.To_json.(
json_dict
(Js.Dict.fromList
[
"static", c.static |> string;
"dynamics", c.dynamics |> js_dict int;
]))
end

let data = {| {
let data =
{| {
"static": "hi",
"dynamics": { "hello": 5, "random": 8 }
} |}

let decodedData =
data |> Json.parseOrRaise
|> Decode.obj
let decodedData = data |> Melange_json.of_string |> Of_json.obj

(*
Will log [ 'hi', { hello: 5, random: 8 } ]
*)
let _ =
decodedData |> Js.log
let _ = decodedData |> Js.log

(*
Will log { static: 'hi', dynamics: { hello: 5, random: 8 } }
*)
let encodedDataBack =
decodedData |> Encode.obj
|> Js.log
let encodedDataBack = decodedData |> To_json.obj |> Js.log
Loading