How to correctly write json options for external npm package function call


#1

Hi,
let’s assume that i have function named “put” which is called with putParams. I can define putParams in TypeScript like this:

type PutParams = {
  TableName: string
  Item: object
  Condition?: string
  Operator?: 'and' | 'or'
  ExpressionNames?: {
    [key: string]: string
  }
  ExpressionValues?: {
    [key: string]: string | number | boolean | any[] | object
  }
}

So now I have definition which required only TableName and Item which can be random object.

const putParams: PutParams = {
  TableName: "myTableNam",
  Item: { id: 1, name: "John Doe" }
}
put(putParams);

or I can define other attributes

const putParams: PutParams = {
  TableName: "myTableNam",
  Item: { id: 1, name: "John Doe" },
  Operator: "and",
  ExpressionNames: { "#name1" : "name1" }
}

Its possible to define similar type in reasonml/bucklescript with optional attributes, union types etc.
I find one possible solution for some optional params with Js.Nullable.t and Js.Dict.t

type putParams = {.
  "ExpressionAttributeNames": Js.Nullable.t(Js.Dict.t(string)),
}

but this define only that JSON data have key as string but value can by any type i set. And I must stil write "ExpressionAttributeNames": Js.Nullable.null if i dont define value.

let pp1 = {
  "ExpressionAttributeNames": Js.Nullable.return(Js.Dict.fromList([
    ("#name1", 10)
  ]))
};

Is there any solution for this and another part of the TypeScript definition to transform it on reasonml?


#2

Looks like bindings to dynamodb.

The key of a dict is always a string, you when say Js.Dict.t(string) it means the values will be of type string. So it corresponds to [key: string]: string.

To avoid typing "ExpressionAttributeNames": Js.Nullable.null when you don’t have a value, you can use https://bucklescript.github.io/docs/en/object.html#special-creation-function

[@bs.obj] external makePutParams : (~_TableName: string, ~_Item: Js.t('a), ~_ExpressionAttributeNames: Js.Dict.t(string)=?, unit) => _ = "";

let putParams = makePutParams(~_TableName="users", ~_Item={"id":1, "name":"John Doe"}, ());
let putParams2 = makePutParams(~_TableName="users", ~_Item={"id":1, "name":"John Doe"}, ~_ExpressionAttributeNames=Js.Dict.fromList([("key", "value")]), ());

And it will generate those javascript objects:

var putParams = {
  TableName: "users",
  Item: {
    id: 1,
    name: "John Doe"
  }
};

var putParams2 = {
  TableName: "users",
  Item: {
    id: 1,
    name: "John Doe"
  },
  ExpressionAttributeNames: Js_dict.fromList(/* :: */[
        /* tuple */[
          "key",
          "value"
        ],
        /* [] */0
      ])
};

I tend to prefer to go through a record. Maybe because I come from OCaml. It’s because I like to have a type to refer to. That I can use to annotate functions for example. I feel it provides more guaranties.

[@bs.deriving jsConverter]
type params('a) = {
  _TableName: string,
  _Item: Js.t('a),
  _ExpressionAttributeNames: Js.Nullable.t(Js.Dict.t(string)),
};

let makeParams = (~tableName, ~item, ~expressionAttributeNames=?, ()) =>
  paramsToJs({
    _TableName: tableName,
    _Item: item,
    _ExpressionAttributeNames:
      Js.Nullable.fromOption(expressionAttributeNames),
  });

let putParams3 =
  makeParams(
    ~tableName="users",
    ~item={"id": 1, "name": "John Doe"},
    ~expressionAttributeNames=Js.Dict.fromList([("key", "value")]),
    (),
  );

let putParams4 =
  makeParams(~tableName="users", ~item={"id": 1, "name": "John Doe"}, ());