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.

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.