Using Map and ppx yojson

Hey, I wonder how one can get it working with a Map. For example,

module M = Map.Make(Int)[@@deriving of_yojson] 

let m = M.empty |> M.add 1 2 |> M.add 3 4 |> M.add 5 6
let s =
  [%yojson_of: int M.t] m
  |> Yojson.Safe.to_string;;

results in

Error: Unbound value M.yojson_of_t

I made it work with the following:


module M = struct
  module Tmp = Map.Make(Int)
  include Tmp
  let yojson_of_t m = Tmp.bindings m |> [%yojson_of: (int * int) list]

end

let m = M.empty |> M.add 1 2 |> M.add 3 4 |> M.add 5 6 
let s =
  [%yojson_of: M.t] m
  |> Yojson.Safe.to_string

is this the way to go?

You can do that, or you can expand the module at the functor level to keep its versatility. Example of expanding Set:

module Set = struct
  module type S = sig
    include Set.S
    val t_of_yojson : Yojson.Safe.t -> t
    val yojson_of_t : t -> Yojson.Safe.t
  end
  module Make(O:OrderedType) = struct
    include Set.Make(O)

    let t_of_yojson (json:Yojson.Safe.t) =
      list_of_yojson O.t_of_yojson json |> of_list

    let yojson_of_t (t:t) =
      to_list t |> yojson_of_list O.yojson_of_t
  end
end

Another example with a nested map:

module M = struct
  module Tmp = Map.Make(Int)
  include Tmp
  let yojson_of_t m = Tmp.bindings m |> [%yojson_of: (int * int) list]
end

let m = 
  M.empty
  |> M.add 1 2
  |> M.add 3 4
  |> M.add 5 6

let s = 
  m
  |> [%yojson_of: M.t] 
  |> Yojson.Safe.to_string

module Nested = struct
  module Tmp = Map.Make(Int)
  include Tmp
  let yojson_of_t m = Tmp.bindings m |> [%yojson_of: (int * M.t) list]
end

let m = 
  M.empty
  |> M.add 1 2
  |> M.add 3 4
  |> M.add 5 6
 
let s =
  Nested.empty
  |> Nested.add 1 m
  |> Nested.add 2 m 
  |> [%yojson_of: Nested.t] 
  |> Yojson.Safe.to_string

When running this piece I get :

Error: Unbound value O.t_of_yojson

I guess I am using wrong OrderedType. I assumed this is Set.OrderedType.

I made it work like this:

(* expand at Functor level *)
module type OrderedType = sig
  type t
  val compare : t -> t -> int
  val t_of_yojson : Yojson.Safe.t -> t
  val yojson_of_t : t -> Yojson.Safe.t 

end

module Set = struct
  module type S = sig
    include Set.S
    val t_of_yojson : Yojson.Safe.t -> t
    val yojson_of_t : t -> Yojson.Safe.t
  end
  module Make(O:OrderedType) = struct
    include Set.Make(O)

    let t_of_yojson (json:Yojson.Safe.t) =
      list_of_yojson O.t_of_yojson json |> of_list

    let yojson_of_t (t:t) =
      to_seq t |> List.of_seq |> yojson_of_list O.yojson_of_t
  end
end

module Int = struct
  type t = int [@@deriving yojson]
  let compare = Stdlib.compare
end

module M = Set.Make(Int)
let s = 
  M.empty 
  |> M.add 1
  |> M.add 2
  |> M.add 3
  |> [%yojson_of: M.t] 
  |> Yojson.Safe.to_string
1 Like

This feels hacky, but for a one-off [%yojson_of] use you just need the right functions with the right names in scope, not even the types:

let yojson_of_int_map yojson_of_value map =
  let module M = Map.Make (Int) in
  [%yojson_of: (int * value) list] (M.bindings map)

let int_map_to_string yojson_of_value map =
  Yojson.Safe.to_string ([%yojson_of: value int_map] map)

The types value and 'a int_map don’t actually exist here, but the PPX produces the right calls to yojson_of_value and yojson_of_int_map yojson_of_value anyway.

However, using an actual alias type 'a int_map = 'a Map.Make (Int).t would also enable other types to use [@@deriving yojson_of] without redefining a full Map module. You can then hide those aliases from your module signature if you want.

1 Like

I did this (just as you did) for both maps and sets, and for yojson and show. And for the latter, I wanted both pp and pp_hum. It’s a pity there’s no way to ā€œaddā€ these extra functions, but then, I guess once you make the new extended functors, you can use them all over the place, so it’s not like there’s a giant expenditure of effort.

1 Like

Hey, Chet, can you share your implementation for a Map: I can’t figure out how to deal with the values being stored:

  type t
  val compare : t -> t -> int
  val t_of_yojson : Yojson.Safe.t -> t
  val yojson_of_t : t -> Yojson.Safe.t 

end

module Map = struct
  module type S = sig
    include Map.S
    val t_of_yojson : Yojson.Safe.t -> 'a t
    val yojson_of_t : 'a t -> Yojson.Safe.t
  end
  module Make(O:OrderedType) = struct
    include Map.Make(O)

    let t_of_yojson (json:Yojson.Safe.t) =
      list_of_yojson O.t_of_yojson json |> List.to_seq |> of_seq

    let yojson_of_t (t:t) =
      to_seq t |> List.of_seq |> yojson_of_list O.yojson_of_t
  end
end;;

the error message:

Error: This expression has type key Seq.t = unit -> key Seq.node
       but an expression was expected of type
         (key * 'a) Seq.t = unit -> (key * 'a) Seq.node
       Type key is not compatible with type key * 'a ```

I also noticed that once I have the yojson ppx enabled, there is no printing in utop. E.g. when I type

>>>> 1234 ;; 

the number isn’t printed

Here’s the file where I did the work: qc-ocaml/lib/qlam_misc.ml at master Ā· chetmurthy/qc-ocaml Ā· GitHub
It’s possible I did work elsewhere in this repo that’s needed.

N.B.: this uses my camlp5-based pa_ppx PPX rewriters, not the standard ppxlib PPX infrastructure. But these are the bog-standard ones, so the code should work with the standard rewriters. Looking at the code, I see that I didn’t implement to_yojson. I don’t remember if this is because there was a problem, or b/c I didn’t need it.

Thanks, Chet. Did you notice that printing doesn’t work in utop when working with yojson ppx enabled?

I never use utop, but also, since all of my code is based on camlp5, the mechanism whereby pretty printing in toplevels happens is probably different from how it works with the normal PPX infrastructure. generally, I use #install_printer And that has worked perfectly fine.

Following this thread, I tried to have this piece of code working but I am stuck into this compilation error. I feel the [@@deriving yojson] is not working as expected, but I can’t manage to solve it.:

File "bin/main.ml", lines 29-32, characters 25-3:
29 | .........................struct
30 |   type t = int [@@deriving yojson]
31 |   let compare = Stdlib.compare
32 | end
Error: Signature mismatch:
       ...
       The value t_of_yojson is required but not provided
       File "bin/main.ml", line 7, characters 2-38: Expected declaration
       The value yojson_of_t is required but not provided
       File "bin/main.ml", line 8, characters 2-38: Expected declaration

The Dune file

(executable
 (public_name myproject)
 (name main)
 (libraries myproject yojson ppx_yojson_conv_lib)
 (preprocess (pps ppx_deriving_yojson)))

Here is the code.

open Ppx_yojson_conv_lib.Yojson_conv.Primitives
open Ppx_yojson_conv_lib.Yojson_conv

module type OrderedType = sig
  type t
  val compare : t -> t -> int
  val t_of_yojson : Yojson.Safe.t -> t
  val yojson_of_t : t -> Yojson.Safe.t 
end

module Map = struct 
  module type S = sig 
    include Map.S  
    val t_of_yojson : Yojson.Safe.t -> 'a t 
    val yojson_of_t : 'a t -> Yojson.Safe.t  
  end 
  module Make(O:OrderedType) = struct
    include Map.Make(O)

    let t_of_yojson (json:Yojson.Safe.t) = 
      let conv = pair_of_yojson O.t_of_yojson (fun x -> x)in 
      of_list (list_of_yojson conv json) 
    
    let yojson_of_t (t:'a t) = 
      yojson_of_list (yojson_of_pair O.yojson_of_t (fun x->x)) (to_list t)
  end
end 

module Int:OrderedType = struct
  type t = int [@@deriving yojson]
  let compare = Stdlib.compare 
end

module IntMap = Map.Make(MyInt)

That doesn’t look like the code that you compiled above? There are no ā€˜@@deriving’ attributes in it? Can you share a complete example? E.g., maybe put it up on GitHub and share a link ?

Typically when I get into these sorts of problems, I will look in the ā€œlogā€ file in the Dune build directory and find the compile directives that cause the problem, and add ā€œ-dsourceā€ to get the source code post-PPX, and then I can try to compile that and debug my way to understanding what went wrong.

You need to scroll down the code snippet to find the last Int module definition with [@@deriving yojson] attributes.

I will definitly look into the log files (didn’t know about it) and the ā€˜-dsource’ option was what I was looking for.

Also post a github or equivalent complete example to help you guys digging further if needed.

It’s been a really long time, but maybe the problem is that when the type is ā€œtā€, you don’t get ā€œt_of_yojsonā€, but instead ā€œof_yojsonā€. I’m just guessing – my laptop is in a bit of a shambles, so I can’t run emacs well enough to tool around in sources to check. But you can verify that by using ā€œ-dsourceā€.

You are a good guesser ! I have checked and that’s indeed what I have seen… plus it looks like to_yojson and of_yojson are recursive functions… ā€œ-dsourceā€ helped a lot for that. Thank you, I am now digging further.

Note that you can do dune describe pp path/to/file.ml to get the preprocessed version of the file without having the dig in the logs.

From what you are saying now it sounds like you have switched from ppx_yojson_conv to ppx_deriving_yojson at some point during development. The names of the functions they generate are different. If you want to work with the Janestreet ecosystem you should probably use ppx_yojson_conv.

Thank you @Khady for the tip on dune : dune describe pp path/to/file.ml I will try that as well. -dsource worked without digging into the logs (printed on the output, but maybe just because it was not compiling…)

Good hint as well about ppx_yojson_conv versus ppx_deriving_yojson. I must confess that I wasn’t really aware of those two flavors of the same lib, hence neither of a mix in my code. That’s what happens when you learn things alone. You get information from various sources but never get the whole picture you would get from an expert/teacher… That will help too !