Search code examples
reasonbucklescriptrescript

How to convert a Js.Dict.t to Js.t in ReScript?


Is there a straight forward way to convert a Js.Dict.t like this one

Js.Dict.fromArray([
    ("bigKey", Js.Dict.fromArray([("smallKey", "value")]))
])

to Js.t like this one:

{
    "bigKey": {
        "smallKey": "value"
    }
}

Solution

  • Structure-wise they're already the same -- both are represented as JavaScript objects -- there's therefore technically no need to "convert" it. Despite being the same structurally the types are different in ways that are mutually exclusive, and there's therefore no way to cast one into the other in general.

    Js.Dict.t is a homogeneous collection of an unknown number of key-value pairs, while Js.t is a heterogeneous collection of a fixed number of key-value pairs, although it also supports subtyping and combined with type inference it might appear to be dynamic.

    So the easiest way to go about it is to just cast it into an "open" object type that will be inferred as whatever you want it to be:

    external dictToJsObject : Js.Dict.t(_) => Js.t({..}) = "%identity";
    

    But beware that there is not type safety on the keys here. The compiler will just assume that however you use it is correct. If you use a key it is assumed to exist, and the type constraint of the value from the Js.Dict.t is not carried over and not consistent across keys.

    A slightly better approach is to cast it into a "closed" object type, with a known shape:

    external dictToJsObject : Js.Dict.t('a) => Js.t({ "foo": 'a, "bar": 'a }) = "%identity";
    

    Here the returned object is specified to have the keys foo and bar with the same value type as the Js.Dict.t. This provides significantly more type safety, but we still cannot know at compile-time that the foo and bar keys actually exist in the Js.Dict.t. We just assume it does.

    Therefore, the only proper way to convert a Js.Dict.t to a Js.t in general is to do so manually:

    let dictToJsObjec
      : Js.Dict.t('a) => option({"foo": 'a, "bar" 'a})
      = dict =>
        switch ((Js.Dict.get(dict, "foo"), Js.Dict.get(dict, "bar"))) {
        | (Some(foo), Some(bar)) => Some({
            "foo": foo,
            "bar": bar,
          })
        | _ => None
        }