# Deriving a record with a map field

**URL:** <https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766>\
**Category:** Learning\
**Created:** [December 7, 2024, 9:06pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766 "2024-12-07T21:06:38Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![NAlec](https://avatars.discourse-cdn.com/v4/letter/n/91b2a8/32.png) [@NAlec](https://discuss.ocaml.org/u/NAlec)\
**Post date:** [December 7, 2024, 9:06pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/1 "2024-12-07T21:06:38Z")

</div>

Hi,

I am trying to derive the following record type with yojson ppx, but it fails and I honnestly don’t understand why. I feel I need to deep dive into polymorphic variants and stuff but I would prefer not to…

Why is the compiler interpreting `int` as a function in that case ?

```auto
module StringMap = struct
  include Map.Make(String) 
  let of_yojson json = 
    let conv p = match p with 
      (k, `Int v) -> (k,v)
      | _ -> failwith "values must be of type int" in 
    match json with 
      `Assoc l -> of_list (List.map conv l)
      | _ -> failwith "Only valide for int value type"
  let to_yojson map = 
    let conv (k,v) = (k, `Int v) in 
    `Assoc (List.map conv (bindings map))
end

type t = {
  m : string;
  v : int StringMap.t;
} [@@deriving yojson]

let () = 
  let a = {|{"m": "value", "v": {"1":1,"2":2}}|} in 
  let b = Yojson.Safe.from_string a in 
  let m = StringMap.of_yojson b in 
  let j = StringMap.to_yojson m in 
  print_endline (Yojson.Safe.to_string j )

```

the compiler says :

```auto
File "bin/main.ml", line 34, characters 6-9:
34 | v : int StringMap.t;
           ^^^
Error: This expression should not be a function, the expected type is
       'a StringMap.t

```

replacing the `int` by a variable type `'a` makes `t` a variable type as well, `type 'a t = ...` but I then get en even more complex error message plus I really don’t need `t` to be variable (or I just need `int StringMap.t`)

---

<div class="post-metadata">

**Author:** ![yawaramin](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/yawaramin/32/3384_2.png) [@yawaramin](https://discuss.ocaml.org/u/yawaramin)\
**Post date:** [December 8, 2024, 2:05am UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/2 "2024-12-08T02:05:10Z")

</div>

Not sure, but I was able to get it to work by specializing the type a bit more:

```auto
module StringMap = Map.Make(String)

module StringInt = struct
  type t = int StringMap.t

  let conv = function
    | k, `Int v -> k, v
    | _ -> failwith "expected int values"

  (* Note, just hacking it and wrapping the result in Ok.
     In a real codebase we would handle the result properly. *)
  let of_yojson = function
    | `Assoc l -> Ok (l |> List.map conv |> List.to_seq |> StringMap.of_seq)
    | _ -> Error "expected JSON object"

  let to_yojson map =
    `Assoc (List.map (fun (k, v) -> k, `Int v) (StringMap.bindings map))
end

type t = { m : string; v : StringInt.t } [@@deriving yojson]

```

This gives me the derived functions:

```auto
val to_yojson : t -> Yojson.Safe.t
val of_yojson : Yojson.Safe.t -> t Ppx_deriving_yojson_runtime.error_or

```

---

<div class="post-metadata">

**Author:** ![andreypopp](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/andreypopp/32/97_2.png) [@andreypopp](https://discuss.ocaml.org/u/andreypopp)\
**Post date:** [December 8, 2024, 8:41am UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/3 "2024-12-08T08:41:08Z")

</div>

Need to make `StringMap`’s of/to yojson functions accept an argument to decode/encode its value type. This is because the type of a map is `type 'a t`, takes a type param and deriving expects that it can pass a decoder/encoder for each of a type param the type has.

```auto
module StringMap = struct
  include Map.Make(String) 
  let of_yojson v_of_json json = 
    let conv (k, v) = (k, v_of_json v) in
    match json with 
    `Assoc l -> of_list (List.map conv l)
    | _ -> failwith "Only valide for int value type"
  let to_yojson v_to_json map = 
    let conv (k, v) = (k, v_to_json v) in 
    `Assoc (List.map conv (bindings map))
end

```

---

<div class="post-metadata">

**Author:** ![NAlec](https://avatars.discourse-cdn.com/v4/letter/n/91b2a8/32.png) [@NAlec](https://discuss.ocaml.org/u/NAlec)\
**Post date:** [December 8, 2024, 5:31pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/4 "2024-12-08T17:31:18Z")

</div>

I fully agree one need such functions, but assuming we do so, how would the yojson deriver manage this additionnal parameter when the `StringMap` has to be derived ?

```auto
type 'a t = { m : string; v : 'a StringMap.t } [@@deriving yojson] 
(*<== where do I provide my value_to_yojson : 'a -> Yojson.Safe.t and yojson_to_value : Yojson.Safe.t -> 'a *)

```

---

<div class="post-metadata">

**Author:** ![andreypopp](https://sea2.discourse-cdn.com/flex020/user_avatar/discuss.ocaml.org/andreypopp/32/97_2.png) [@andreypopp](https://discuss.ocaml.org/u/andreypopp)\
**Post date:** [December 8, 2024, 5:49pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/5 "2024-12-08T17:49:21Z")

</div>

```auto
type 'a t = { m : string; v : 'a StringMap.t } [@@deriving yojson] 

```

so in this case a function of the following type will be generated:

```auto
val to_yojson : ('a -> json) -> 'a t -> json

```

which you then will use like this

```auto
let x : int t = ...
let json = to_yojson int_to_yojson x

```

It might be useful to check the generated by ppx code in the editor/IDE. I know VSCode has such functionality. If your editor doesn’t then change `[@@deriving ...]` to `[@@deriving_inline ...]` and observe the generated code pasted back after you do `dune build --auto-promote`

---

<div class="post-metadata">

**Author:** ![NAlec](https://avatars.discourse-cdn.com/v4/letter/n/91b2a8/32.png) [@NAlec](https://discuss.ocaml.org/u/NAlec)\
**Post date:** [December 8, 2024, 6:29pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/6 "2024-12-08T18:29:17Z")

</div>

Your solution is what I was looking for since a few days…thank you !

Also I noticed (although @Khady already pointed it) that I had mixed ppx\_yojson\_conv and ppx\_deriving\_yojson …

---

<div class="post-metadata">

**Author:** ![NAlec](https://avatars.discourse-cdn.com/v4/letter/n/91b2a8/32.png) [@NAlec](https://discuss.ocaml.org/u/NAlec)\
**Post date:** [December 8, 2024, 9:36pm UTC](https://discuss.ocaml.org/t/deriving-a-record-with-a-map-field/15766/7 "2024-12-08T21:36:31Z")

</div>

With your help, I finally reach the general case which I post below for others

```auto
open Ppx_yojson_conv_lib.Yojson_conv

module StringMap : sig 
  include Map.S with type key = string
  val t_of_yojson : (Yojson.Safe.t -> 'a) -> Yojson.Safe.t -> 'a t
  val yojson_of_t : ('a -> Yojson.Safe.t) -> 'a t -> Yojson.Safe.t
end = struct
  include Map.Make(String)

  let t_of_yojson value_of_yojson json = 
    let conv (k,v) = (k, value_of_yojson v) in 
    match json with 
      |`Assoc l -> of_list (List.map conv l)
      | _ -> failwith "input must be of type `Assoc (string * Yojson.Safe.t) list"
  let yojson_of_t yojson_of_value map = 
    let conv (k,v) = (k,yojson_of_value v) in  
    `Assoc (List.map conv (bindings map))
end

type toto = {
  m : string;
  v : int StringMap.t;
} [@@deriving yojson]

let () = 
  let a = {|{"m": "value", "v": {"1":1,"2":2}}|} in 
  let b = Yojson.Safe.from_string a in 
  let c = toto_of_yojson b in 
  let j = yojson_of_toto c in 
  print_endline (Yojson.Safe.to_string j )

```
