Convert Yojson to object?

Is there a way to convert a Yojson Assoc to an OCaml object, whose methods correspond to the Assoc items, and do so recursively, so that if an item contains another Assoc, you get an object for that?

Why would you want to do that?

Call me crazy, but an API I’m using demands exactly that.

Can you provide any specifics? To me it doesn’t make sense for an API to ask for an arbitrary OCaml object. If it takes an arbitrary object, there’s almost nothing useful it can do with it. I’m guessing there’s more to it, like e.g. it actually wants an object with specific fields? There are well-known ways to decode a Yojson value into a strongly-typed OCaml value, e.g. ppx_yojson_conv (although I don’t think these PPXs support decoding into objects). Or you could even do a manual conversion, e.g. say you want to convert:

{"a": 1, "b": true}

to:

object
  method a = 1
  method b = true
end

You could do something like:

let ( .$[] ) json key = match json with
  | `Assoc assoc -> begin try List.assoc key assoc with _ -> `Null end
  | _ -> `Null

let decode json = match json.$["a"], json.$["b"] with
  | `Int a, `Bool b ->
    object
      method a = a
      method b = b
    end
  | _ ->
    failwith "Error decoding JSON"

To add to @yawaramin’s answer, no, you cannot do that. The definition of the data has to be on the OCaml side (record or object) and can then be converted to JSON. OCaml isn’t a dynamic language like python, where objects are just dictionaries and therefore you can build them off of what’s in a json dictionary.

2 Likes

I’ve tried doing something with CamlinternalOO but I haven’t been able to create an object. I think that all the tools are here, except for something that takes a CamlinternalOO.obj and returns an object. CamlinternalOO.create_table takes a string array and returns a table, CamlinternalOO.copy takes an object and returns one, CamlinternalOO.create_obj takes a table and returns a CamlinternalOO.obj.

Another option may be to use the Obj module to see how an object with methods looks like at runtime, and build them “yourself”, from raw bytes.

I haven’t been able to find an example of how to build an object at runtime in OCaml, so maybe it’s not possible even with all the building blocks here. For example, CamlinternalOO uses caml_set_oo_id when duplicating an object, and this function isn’t exported. My knowledge and exploration of the OCaml internals stop here.

Edit: after a bit more investigation, I believe you should be able to do something with create_table and add_initializer, but I’m not sure how initializers work. They are functions that take an obj, which is here an array of t, which is DummyA | DummyB | DummyC of int. I don’t know what they do with these values, I can’t find any match on them on the OCaml codebase.

What you’re recommending is trying to completely bypass the type system, which is really not a good idea. In any case, it still wouldn’t work, because even if you constructed what an object looked like in the runtime at the low level (which again is a very bad idea), it would still not be able to pass the typechecker’s test. There simply isn’t a way to do this at runtime in OCaml by design.

1 Like

Perhaps there is a mis-understanding of the need of OCaml objects? Assuming it’s a reasonably normal API it takes and gives JSON, so you if you need to deal with dynamic shapes you are likely best off just dealing with it yojson’s types.

There is Util module that has lots of helper functions if that’s the case Util (yojson.Yojson.Safe.Util).

That’s true, but that seems to be the only way to do what they are asking. Other people have already covered the “why you shouldn’t do that”, I’m trying to complete this response by adding the “if you really want to do this, here’s how”. As I said, I haven’t been able to find any info on how to create an object at runtime in OCaml, this is a good occasion to put that information somewhere where people can find it.

There’s Obj.magic for that. An example with variant types:

type type1 = Toto | Tata
type type2 = Tutu | Titi
let a = Toto
let b = Tutu

let f = function
| Toto -> 0
| Tata -> 10

let () =
    Printf.printf "f a is: %d" (f a);
    print_newline ();
    Printf.printf "f b is: %d" (f (Obj.magic b))

(*
Will print:
f a is: 0
f b is: 0
*)

type1 and type2 have the same representation at runtime, so this will work. My suspicion is that you can do the same for objects (using CamlinternalOO and Obj), though from what I understand objects have a unique ID that represents their identity, so you’ll need that too.

All of that leads me to believe that this is indeed possible to do this. Should you do this? That’s another question entirely. But I think that it’s important to answer both “is this possible, how?” and “should you do this?”.

Notice that you still had to predefine the types to use Obj.magic to convert to them. Somewhere in the OCaml code, the object needs to be defined for the typechecker.

Except that you are proposing to use compiler internals, which come with no guarantee of backward compatibility, break type safety, and may cause other issues if you break a runtime invariant. Neither Obj nor CamlinternalOo should be used in regular code.

In other words, you are trying to write code that might break on every OCaml version without a good reason.

This is even more true when the initial idea is based on a misconception: json “objects” are not objects in the OCaml sense in any way: they don’t have method that call other methods on an implicit self, they don’t have internal hidden state, they don’t have polymorphic methods.

Json “objects” are in fact heterogeneous maps not objects, and if one want to type them, one should use a typed heterogeneous map interface.

3 Likes

It looks like topic starter’s task requires low-level construction of OCaml objects, probably from C. Unfortunately, it is not well documented, and usually discouraged. Depite, I would be glad to see the result implemented in a way desired by @Paul_A_Steckler. It could (to say at least) lead to improving of OCaml’s low-level documentation. Good luck!

From manual:

Objects are represented as blocks with tag Object_tag. The first field of the block refers to the object’s class and associated method suite, in a format that cannot easily be exploited from C. The second field contains a unique object ID, used for comparisons. The remaining fields of the object contain the values of the instance variables of the object. It is unsafe to access directly instance variables, as the type system provides no guarantee about the instance variables contained by an object.

It’s not possible to create OCaml objects either with Obj or from C, because they contain functions and it’s not possible to create functions at runtime. You can get around this by generating temporary modules on the fly, compiling them and dynlinking them, but that’s a lot of effort for something that probably has a better solution anyway.

1 Like

Could you explain more details of your problem? Maybe we can help you find a solution ?

1 Like

Well, this query has provoked a fair bit of discussion!

The API in question is generated by graphql_ppx. The GraphQL endpoint accepts input over the Web in a format that can be converted to an OCaml data structure. But if you access that endpoint programatically, it expects an OCaml object.

Workaround: define a different endpoint as a GraphQL “scalar”. In that case, you can feed the endpoint JSON, which can be converted to an instance of an OCaml type.

It’s possible to construct the OCaml object from the JSON by writing explicit code, we do know the structure of that object. But that code would be huge, so it would be nice to derive the object automatically.

I had supposed this task wasn’t possible, but it never hurts to ask.

This doesn’t really make sense to me. You’re using GitHub - reasonml-community/graphql-ppx at v0.7.1 ? This is basically a codegen tool to make it easy to write GraphQL queries in OCaml/Reason. It lets you create and send a query and handle the response. It automatically parses responses, which are JSON objects, into the corresponding shaped OCaml object. So in a sense, what you’re asking for is already done by this PPX.

The module created by the use of %graphql contains a make function that takes an object, which contains other objects, if you define the endpoint’s input type using the obj function.

Can you refresh my memory on what the obj function is? I’m not seeing it in the readme and not being able to find it in the source code. I’m seeing that the codegen module normally contains a make function which takes labelled arguments, and a makeWithVariables function that takes a Js.t object. But the latter should only work in BuckleScript/ReScript. Are you using OCaml native (dune/opam) or ReScript?

Using OCaml.

val obj : ?doc:string 
             -> string 
            -> fields:('a, 'b) arg_list 
            -> coerce:'b -> 'a option arg_typ