Accepting Generic Objects?

Playground example: https://rescript-lang.org/try?version=v10.1.2&code=DYUwLgBAtghg1iAymATgSwHYHMIF4IAUA9gEYBWAXBAOQwCUVAzqpjrgHwQDeAUBBGgBmEAFKMAdABUAngAcQEsArDFyAbQBECaRoC6AGgjJ02Otz78IpMpu16LAXwghgjEOcuiJAeVlg0RBjiWOAAogAeGARi4mKB4swmWELSAIIY0qpkdHSOPA48PKCQsJh4hGYcHtDwSCzYBFwaSswaVAAsDrkOQA

I want to write a function which accepts a generic JavaScript object and makes a simple check: does the incoming object have a string-type property called “key”? If so → behavior A, else → behavior B. Making this property check convinces ReScript’s type system that the incoming object must have a particular shape, which breaks the compilation if trying to call into the function with an object that does not meet that shape. But the point is that the function is actually not expecting an object of any particular shape, it will check for what it needs and act accordingly. How do I express that?

Thanks

2 Likes

For a generic object you want to use Js.Dict. This is an object with a known type for the keys, but you don’t know what the keys are.

let getKey= (obj: Js.Dict.t<'a>): option<'a> => {
  obj->Js.Dict.get("key")
}

let main = () => {
  getKey([("test", "foo")]->Js.Dict.fromArray)
}

Here’s a playground example: https://rescript-lang.org/try?version=v10.1.2&code=DYUwLgBA5uDSIE8IF4IAoD2AjAVgLggCkBnAOgBEBLAYzFLAB4ByAQwD4BKAjABzEowA7ZuxRsIAbwBQECNhwBaNiQo06MMGgBEAa0RaOUgL5SpoSAFsWlQSnQcxkmdDiI0AbW1gQxMFoA0EFoAZhgYBgC6SipUtKTBAE4YFgCCCQksCIYmQA

2 Likes

You can also use {..} for any object, but it will only work for objects that actually have the key you are asking for.

let getId = (obj: {..}): 'a => {
  obj["id"]
}

let main = () => {
  getId({ "id": "foo" })
}

https://rescript-lang.org/try?version=v10.1.2&code=DYUwLgBA5uCSAmEC8EAUB7ARgKwFwQG8A6IgXwEp8ByAQ2QD5CAoCCLbAbQCIBLeLgLpNSTJqEgBbGjwB2yNOQbNWMMAlQFe-fFwBm6dFwrCmQA

An aside, but I think it’s worth mentioning that one can always utilise %raw (or simply a utility function in JS/TS to which you create an external). It’s perhaps especially useful in scenarios similar to this

If you don’t mind being fast and loose (and unsafe type-wise), you can just do a little external. (playground)

@get external key: 'a => string = "key"

let makeString = (obj: 'a): string => {
  if Js.Types.test(obj->key, String) {
    obj->key
  } else {
    Js.Option.getExn(Js.Json.stringifyAny(obj))
  }
}

let main = () => {
  makeString({"key": 123})->ignore
  makeString({"test": 4})->ignore
}
1 Like

Thanks all! This gives me plenty of options

1 Like