Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
61 changes: 39 additions & 22 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
## Unpublished

- **[breaking]** Library, PPX: Unify runtimes (`*.ppx-runtime` libraries are
removed, can use `melange-json` and `melange-json-native` instead), replace
`Json` module with `Melange_json`, deprecate `Decode` and `Encode` modules,
introduce new decoding error type and helper function
`of_json_error_to_string`.
([#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
tried.
([#32](https://github.com/melange-community/melange-json/pull/32))
tried. ([#32](https://github.com/melange-community/melange-json/pull/32))
- **[breaking]** Json.Decode.DecodeError exception now contains a variant type
as payload instead of a string.
([#32](https://github.com/melange-community/melange-json/pull/32))
- **[breaking]** PPX: Rename `[@json.as]` to `[@json.name]`
([#23](https://github.com/melange-community/melange-json/pull/23))
- **[breaking]** PPX: Drop special encoding for enumeration-like variants (variants with each
constructor having no arguments).
- **[breaking]** PPX: Drop special encoding for enumeration-like variants
(variants with each constructor having no arguments).
([#26](https://github.com/melange-community/melange-json/pull/26))
- **[breaking]** PPX: change JSON representation of polyvariants, make it compatible with
ppx_deriving_yojson and ppx_yojson_conv
- **[breaking]** PPX: change JSON representation of polyvariants, make it
compatible with ppx_deriving_yojson and ppx_yojson_conv
([#27](https://github.com/melange-community/melange-json/pull/27))
- **[breaking]** PPX: Consistent use of exceptions in runtime.
([#28](https://github.com/melange-community/melange-json/pull/28))
Expand All @@ -26,8 +31,7 @@
- PPX: Add `yojson` as runtime dep for the native version
([#15](https://github.com/melange-community/melange-json/pull/15))
- PPX: add `[@@json_string]` for deriving converters to/from JSON strings
directly
([#30](https://github.com/melange-community/melange-json/pull/30))
directly ([#30](https://github.com/melange-community/melange-json/pull/30))
- PPX: add support for `int64` in the runtime
([#33](https://github.com/melange-community/melange-json/pull/33))
- PPX: remove `string_to_json` usage on js side
Expand All @@ -41,8 +45,7 @@
([#11](https://github.com/melange-community/melange-json/pull/11))
- Add `melange-json-native` package
([#12](https://github.com/melange-community/melange-json/pull/12))
- Add `[@drop_default]` attribute to drop `None` values from JSON
representation
- Add `[@drop_default]` attribute to drop `None` values from JSON representation
([#17](https://github.com/melange-community/melange-json/pull/17))

## 1.2.0 (2024-08-16)
Expand All @@ -68,47 +71,59 @@
* Added `Json.Decode.id`

### 5.0.1
* Dual licensed as LGPL-3.0 and MPL-2.0. MPL is mostly equivalent to LGPL but relaxes its restriction on linking, which works better with the JavaScript packaging and distribution model.
* Dual licensed as LGPL-3.0 and MPL-2.0. MPL is mostly equivalent to LGPL but
relaxes its restriction on linking, which works better with the JavaScript
packaging and distribution model.

### 5.0.0
* Removed deprecated `arrayOf` encoder
* Renamed `dict` encoder to `jsonDict`
* Added new `dict` encoder that takes an additional encoder argument used to encode the contained values, and so it's consistent with the respective `dict` decoder.
* Added new `dict` encoder that takes an additional encoder argument used to
encode the contained values, and so it's consistent with the respective `dict`
decoder.

### 4.0.0
* Bumped `bs-platform` peer dependency to 5.0.4 to stop the compiler's complaining.
* Bumped `bs-platform` peer dependency to 5.0.4 to stop the compiler's
complaining.

### 3.0.0
* Replace usage of `Js.Date.toJSON` with `Js.Date.toJSONUsafe`, which is exactly the same, just to avoid deprecation warnings for end users (Thanks Bob!)
* Replace usage of `Js.Date.toJSON` with `Js.Date.toJSONUsafe`, which is exactly
the same, just to avoid deprecation warnings for end users (Thanks Bob!)
* Requires `bs-platform` >= 4.0.2

### 2.0.0
* Removed `Json.Decode.boolean`, `Json.Encode.boolean`, `Json.Encode.booleanArray`
* Removed `Json.Decode.boolean`, `Json.Encode.boolean`,
`Json.Encode.booleanArray`
* Requires `bs-platform` >= 3.0.0

### 1.3.1
* Reverted commits that broke backwards compatibility despite only affecting the implementation
* Reverted commits that broke backwards compatibility despite only affecting the
implementation

### 1.3.0
* Deprecated `Json.Decode.boolean`, `Json.Encode.boolean`, `Json.Encode.booleanArray`
* Deprecated `Json.Decode.boolean`, `Json.Encode.boolean`,
`Json.Encode.booleanArray`
* Added `Json.Encode.boolArray`

### 1.2.0
* Added `Json.Encode.char` and `Json.Decode.char`

### 1.1.0
* Added "stack traces" to higher-order decoders, making it easier to find the location of an error.
* Added "stack traces" to higher-order decoders, making it easier to find the
location of an error.

### 1.0.1
* Moved repository from `reasonml-community/bs-json` to `glennsl/bs-json`
* Renamed NPM package from `bs-json` to `@glennsl/bs-json`

### 1.0.0
* Replaced `Json.Encoder.array` with `Json.Encode.arrayOf` renamed to `array`. Deprecated `arrayOf` alias.
* Replaced `Json.Encoder.array` with `Json.Encode.arrayOf` renamed to `array`.
Deprecated `arrayOf` alias.
* Added `Json.parse`, `Json.parseOrRaise`, `Json.stringify`
* Added `date` encoder and decoder
* Added `tuple2`/`tuple3`/`tuple4` encoders and decoders
* Fixed bug where js integers > 32-bit were rejected as integers by Json.Decode.int (#15)
* Fixed bug where js integers > 32-bit were rejected as integers by
Json.Decode.int (#15)

### 0.2.4
* Added `Json.Encode.bool`
Expand All @@ -120,7 +135,8 @@
* Deprecated `Json.Encode.array`

### 0.2.3
* Fixed embarrassing bug where an API was used that isn't available on IE (honestly more embarrassed on behalf of IE though)
* Fixed embarrassing bug where an API was used that isn't available on IE
(honestly more embarrassed on behalf of IE though)

### 0.2.2
* Added `Json.Decode.pair`
Expand All @@ -130,4 +146,5 @@

### 0.2.0
* Breaking: Renamed `Json.Encode.object_` to `Json.Encode.dict`
* Added `Json.Encode.object_` taking a list of properties instead of a Json.Dict.t as before
* Added `Json.Encode.object_` taking a list of properties instead of a
Json.Dict.t as before
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)))
Loading