Json to Belt.Map

What is the best way to go from Js.Json to Belt.Map?

I need more control over the fetched object that JS.Dict does not provide

Edit: to be more clear, I’m trying to do something like this post from @johnj where the internal object is updated to have a new shape. I’d imagine this is what Belt.Map.String.update() is for.

THis is the shape of the object

type foo = Js.Dict.t<{ 
  "role": string, 
  "name": string, 
  "inviteLink": string, 
  "count": int
}>

What have you tried so far?

I’m using rescript-json-combinators to decode the json

type guild = {"role": string, "name": string, "inviteLink": option<string>}

type guilds = Js.Dict.t<guild>

let guild = Json.Decode.object(field => {
  "role": field.required(. "role", Json.Decode.string),
  "name": field.required(. "name", Json.Decode.string),
  "inviteLink": field.optional(. "inviteLink", Json.Decode.string),
})
let guilds = guild->Json.Decode.dict

This gives the type

JsonCombinators.Json.Decode.t<
 Js.Dict.t<
   {
     "inviteLink": option<string>,
     "name": string,
     "role": string,
   },
 >,
>

There is no good way to map the returned record to a Map

I’m now trying Js.Obj.assign which seems to be a decent workaround

I ended up using Js.Obj.assign

let replaceEntry = (~config, ~key, ~entry) => {
   let {id, name, token, decoder} = config
   ReadGist.content(~token, ~id, ~name, ~decoder)
   ->then(content => {
     let prev = content->Js.Dict.get(key)->Belt.Option.getExn
     let new = prev->Js.Obj.assign(entry)

     content->Js.Dict.set(key, new)
     let content = content->Js.Json.stringifyAny

     let files = Js.Dict.empty()
     files->Js.Dict.set(name, {"content": content})

     let body = {
       "gist_id": gistId,
       "description": "Update guilds",
       "files": files,
     }

     let params = {
       "method": "PATCH",
       "headers": {
         "Authorization": `token ${token}`,
         "Accept": "application/vnd.github+json",
       },
       "body": body->Js.Json.stringifyAny,
     }

     `https://api.github.com/gists/${gistId}`
     ->fetch(params)
     ->then(res => {
       switch res->Response.status {
       | 200 => Ok(200)->resolve
       | status => {
           res
           ->Response.json
           ->then(
             json => {
               Js.log2(status, json->Json.stringify)
               resolve()
             },
           )
           ->ignore
           Error(Response.PatchError)->resolve
         }
       }
     })
   })
   ->catch(e => {
     Js.log2("e: ", e)
     resolve(Error(e))
   })

With rescript-struct it would look like this:

type guild = {role: string, name: string, inviteLink: option<string>}

let guildStruct =
  S.record3(.
    ("role", S.string()),
    ("name", S.string()),
    ("inviteLink", S.option(S.string())),
  )->S.transform(
    ~parser=((role, name, inviteLink)) => {role: role, name: name, inviteLink: inviteLink}->Ok,
    (),
  )

let guildsStruct =
  S.dict(guildStruct)->S.transform(
    ~parser=guildsDict => guildsDict->Js.Dict.entries->Belt.Map.String.fromArray->Ok,
    (),
  )

data->S.parseWith(guildsStruct)

The guildsStruct has the type

S.t<Belt.Map.String.t<guild>>