Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookbook deserialise and post-process YAML recipes #2372

Merged
merged 5 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
78 changes: 78 additions & 0 deletions data/cookbook/deserialise-post-process-from-yaml/00-yaml.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
---
packages:
- name: yaml
tested_version: 3.2.0
used_libraries:
- yaml
- name: ppx_deriving_yaml
tested_version: 0.2.2
used_libraries:
- ppx_deriving_yaml
---

(* This YAML string contains a list of ingredients where the ingredients are represented as
a YAML object, with keys representing names and values representing amounts. *)
let yaml_string = {|
french name: pâte sucrée
ingredients:
- flour: 250
- butter: 100
- sugar: 100
- egg: 50
- salt: 5
steps:
- soften butter
- add sugar
- add egg and salt
- add flour
|}

(* The `[@@deriving of_yaml]` attribute makes the `ppx_deriving_yaml` library generate the function
``ingredient_of_yaml : Yaml.value -> (ingredient, [> `Msg of string]) result``. *)
type ingredient = {
name: string;
weight: int;
} [@@deriving of_yaml]

(* The `[@@deriving of_yaml]` attribute makes the `ppx_deriving_yaml` library generate the function
``recipe_of_yaml : Yaml.value -> (ingredient, [> `Msg of string]) result``. *)
type recipe = {
name: string; [@key "french name"]
ingredients: ingredient list;
steps: string list;
} [@@deriving of_yaml]

(* Since the structure of the YAML file does not exactly match the `recipe` type,
we (1) parse the YAML file to the internal representation `Yaml.value` of the `yaml` package,
and then (2) change the structure to match the `recipe` type, so we can use the `recipe_of_yaml`
function.

The functions `add_keys` and `at_ingredients` perform this post-processing. *)
let add_keys = function
| `O [(name, `Float weight)] ->
`O [
("name", `String name);
("weight", `Float weight);
]
| v -> v

let at_ingredients f = function
| `O [
("french name", `String name);
("ingredients", `A ingredients);
("steps", `A steps)
] -> `O [
("french name", `String name);
("ingredients",
Yaml.Util.map_exn f (`A ingredients));
("steps", `A steps);
]
| v -> v

(* Parse, post-process, and convert the YAML string into an OCaml value of type `recipe`. *)
let pate_sucree =
yaml_string
|> Yaml.of_string
|> Result.map (at_ingredients add_keys)
|> fun yaml -> Result.bind yaml recipe_of_yaml

79 changes: 79 additions & 0 deletions data/cookbook/deserialise-post-process-from-yaml/01-hl_yaml.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
packages:
- name: hl_yaml
tested_version: 1.0.0
used_libraries:
- hl_yaml
- name: ppx_deriving_yojson
tested_version: 3.7.0
used_libraries:
- ppx_deriving_yojson
---

(* This YAML string contains a list of ingredients where the ingredients are represented as
a YAML object, with keys representing names and values representing amounts. *)
let yaml = {|
french name: pâte sucrée
ingredients:
- flour: 250
- butter: 100
- sugar: 100
- egg: 50
- salt: 5
steps:
- soften butter
- add sugar
- add egg and salt
- add flour
|}

(* The `[@@deriving of_yojson]` attribute makes the `ppx_deriving_yojson` library generate the function
`ingredient_of_yojson : Yojson.Safe.t -> (ingredient, string) result`. *)
type ingredient = {
name: string;
weight: int;
} [@@deriving of_yojson]

(* The `[@@deriving of_yojson]` attribute makes the `ppx_deriving_yojson` library generate the function
``recipe_of_yojson : Yojson.Safe.t -> (ingredient, string) result``. *)
type recipe = {
name: string; [@key "french name"]
ingredients: ingredient list;
steps: string list;
} [@@deriving of_yojson]

(* Since the structure of the YAML file does not exactly match the `recipe` type,
we (1) parse the YAML file to the representation `Yojson.Safe.t`,
and then (2) modify the `Yojson.Safe.t` value to change the structure to match the `recipe` type,
so we can use the `recipe_of_yojson` function.

The functions `add_keys` and `at_ingredients` perform this post-processing. *)
let add_keys = function
| `Assoc [(name, `Int weight)] ->
`Assoc [
("name", `String name);
("weight", `Int weight);
]
| v -> v

let at_ingredients f = function
| `Assoc [
("french name", `String name);
("ingredients", `List ingredients);
("steps", `List steps)
] -> `Assoc [
("french name", `String name);
("ingredients",
Yojson.Safe.Util.map f (`List ingredients));
("steps", `List steps);
]
| v -> v

(* Parse, post-process, and convert the YAML string into an OCaml value of type `recipe`. *)
let pate_sucree =
let of_yojson json =
json
|> at_ingredients add_keys
|> recipe_of_yojson in
yaml
|> Hl_yaml.Unix.parse ~of_yojson
4 changes: 4 additions & 0 deletions data/cookbook/tasks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,12 @@ categories:
slug: serialise-to-yaml
- title: Deserialise YAML Data
slug: deserialise-from-yaml
description: |
Deserialise a YAML file whose structure maps to some OCaml types.
- title: Deserialise and Post-Process YAML Data
slug: deserialise-post-process-from-yaml
description: |
Deserialise a YAML file whose structure does not directly map to some OCaml types.
- title: Date and Time
tasks:
- title: Get Today's Date
Expand Down
Loading