Pass object that conforms to a specific shape/interface to JS function

I’m using a UI library that lets me render lists of items but each item needs to have a key property. In TypeScript, this is defined like…

export interface IObjectWithKey {
    key?: string | number;
}

How can I pass an object from ReScript that conforms to this interface? I’ve tried a couple things and can’t do it. I’m assuming it can’t be done. If so, please let me know so I’ll stop thinking about this.

This attempt below doesn’t work. I can change the {"key":string} to 'a and it will work since anything is accepted but I’m hoping for some compile-time checks.

@module("ui library")
external makeTableA: array<{"key": string}> => unit = "makeTableA"

type person = {"key": string, "first": string, "last": string}
let mike: person = {"key": "561-96", "first": "mike", "last": "jones"}
let bob: person = {"key": "332-53", "first": "bob", "last": "smith"}

 // syntax error, wants a {"key":string}
makeTableA([mike, bob])

I also tried using modules. This compiles but it has a couple serious problems. (1) I don’t think I can construct a module for each piece of data; the data is dynamic and I think functors only work with constants. (2) The compiled JS doesn’t pass the entire object/module to the javascript side; just the key. I need the whole object identity passed since my table rendering function uses all the data not just the key.

module type KeyedObject = {
  let key: string
}
@module("ui library")
external makeTableB: array<module(KeyedObject)> => unit = "makeTableB"
module type PersonType = {
  let first: string
  let last: string
  let key: string
}
module Mike: PersonType = {
  let first = "mike"
  let last = "jones"
  let key = "561-96"
}
module Bob: PersonType = {
  let first = "bob"
  let last = "smith"
  let key = "332-53"
}
makeTableB([module(Mike), module(Bob)])

Hi would this work

@module("ui library")
external makeTableA: array<{.."key": string}> => unit = "makeTableA"

type person = {"key": string, "first": string, "last": string}
let mike: person = {"key": "561-96", "first": "mike", "last": "jones"}
let bob: person = {"key": "332-53", "first": "bob", "last": "smith"}

makeTableA([mike, bob])

Playground link

Awesome! Didn’t know you could do that. One problem though. It would be nice to be able to pass a ReScript record with the same shape but it doesn’t work - compile error. Do you know why that isn’t possible?

It’s because objects and records are different types.

You could use @obj to create objects. docs about @obj.

@module("ui library")
external makeTableA: array<{.."key": string}> => unit = "makeTableA"

module Person = {
  type t = {key: string, first: string, last: string}

  @obj
  external toObj: (~key: string, ~first: string, ~last: string) => _ = ""

  let convertToObject = person =>
    toObj(~key=person.key, ~first=person.first, ~last=person.last)
}
let mike: Person.t = {key: "561-96", first: "mike", last: "jones"}
let bob: Person.t = {key: "332-53", first: "bob", last: "smith"}

makeTableA(
  [mike, bob]->Belt.Array.map(person => Person.convertToObject(person)),
)

PlayGround Link