Is there any convenient way to generate ReScript type from JSON string?

Like, in the case of rescript-promise, we know that the http response would look like:

{"token":"QpwL5tke4Pnpja7X3", "error":""}

So we can easily define the external fetch and type response:

    type response = {"token": Js.Nullable.t<string>, "error": Js.Nullable.t<string>}

    @val @scope("globalThis")
    external fetch: (
        string,
        'params,
    ) => Promise.t<Response.t<{"token": Js.Nullable.t<string>, "error":Js.Nullable.t<string>}>> = "fetch"

But life may not that easy, some response would be complicated, and in go world, we use tools like json-to-to, and generate go struct like:

type AutoGenerated struct {
	Rc     int    `json:"rc"`
	Rt     int    `json:"rt"`
	Svr    int    `json:"svr"`
	Lt     int    `json:"lt"`
	Full   int    `json:"full"`
	Dlmkts string `json:"dlmkts"`
	Data   struct {
		Total int `json:"total"`
		Diff  []struct {
			F9    int     `json:"f9"`
			F12   string  `json:"f12"`
			F13   int     `json:"f13"`
			F14   string  `json:"f14"`
			F20   int64   `json:"f20"`
			F23   int     `json:"f23"`
			F58   float64 `json:"f58"`
			F130  float64 `json:"f130"`
			F131  float64 `json:"f131"`
			F132  float64 `json:"f132"`
			F133  float64 `json:"f133"`
			F134  string  `json:"f134"`
			F137  float64 `json:"f137"`
			F138  float64 `json:"f138"`
			F152  int     `json:"f152"`
			F1020 int     `json:"f1020"`
			F1113 int     `json:"f1113"`
			F1045 int     `json:"f1045"`
			F1009 int     `json:"f1009"`
			F1023 int     `json:"f1023"`
			F1049 int     `json:"f1049"`
			F1129 int     `json:"f1129"`
			F1037 int     `json:"f1037"`
			F1135 int     `json:"f1135"`
			F1115 int     `json:"f1115"`
			F1058 int     `json:"f1058"`
			F1132 int     `json:"f1132"`
			F1130 int     `json:"f1130"`
			F1131 int     `json:"f1131"`
			F1137 int     `json:"f1137"`
			F1133 int     `json:"f1133"`
			F1138 int     `json:"f1138"`
			F3020 int     `json:"f3020"`
			F3113 int     `json:"f3113"`
			F3045 int     `json:"f3045"`
			F3009 int     `json:"f3009"`
			F3023 int     `json:"f3023"`
			F3049 int     `json:"f3049"`
			F3129 int     `json:"f3129"`
			F3037 int     `json:"f3037"`
			F3135 int     `json:"f3135"`
			F3115 int     `json:"f3115"`
			F3058 int     `json:"f3058"`
			F3132 int     `json:"f3132"`
			F3130 int     `json:"f3130"`
			F3131 int     `json:"f3131"`
			F3137 int     `json:"f3137"`
			F3133 int     `json:"f3133"`
			F3138 int     `json:"f3138"`
		} `json:"diff"`
	} `json:"data"`
}

I was wondering if there is any tool like this in ReScript world?

Something I’ve played with in the past:

It does not exactly generate types though. It generates something that is a little more expressive than normal types, thought it’s pretty evident what the underlying type would look like. Just, there’s no automation to convert that into strictly a ReScript type.

1 Like

Borrowing my response from last time this came up:

1 Like

There are quite many libraries, where you can create a decoder with code:

Personally I consider rescript-struct the best among them, but I’m biased since I’m the creator :stuck_out_tongue_closed_eyes:

Also, there’s decco which is the most similar to what you have in Go. But ReScript team members voiced some concerns about the tool. Using Variants with data from a JSON http request - #13 by chenglou

Still haven’t tried Spyder’s bs-atdgen-codec-runtime, so can’t say anything about it.

1 Like

yeah I need to find time to put together a good sample project, probably in that benchmark repo, for atd. Decoders with code are great but having something generate that code for you is even better :stuck_out_tongue:

The atdgen runtime is based on bs-json, and I don’t own it. I just took over maintenance of the project to publish atdgen executables on NPM.

1 Like

The benifit of decoders with code, is that you can describe data migrations from one data structure to another. While with codegened decoders it should be done in additional step. But I 100% agree that the DX of using something like decco is so much better :blush:

The decco seems fit to you. I’ve forked the decco and changed some usage for the variants with https://github.com/green-labs/ppx_spice

It seems like I should spend some time to learn/guess using decco.

According the readme, my bsconfig:

{
  "name": "rescript-project-template",
  "version": "0.0.1",
  "sources": {
    "dir" : "src",
    "subdirs" : true
  },
  "package-specs": {
    "module": "commonjs",
    "in-source": true
  },
  "suffix": ".bs.js",
  "bs-dependencies": [    
    "@greenlabs/ppx-spice"
  ],
  "warnings": {
    "error" : "+101"
  },
  "ppx-flags": [   
    "@greenlabs/ppx-spice/ppx"
  ]
}

Then for the r101.res

@spice
type t = {
  @spice.key("spice-label") label: string,
  @spice.key("spice-value") value: int,
}

let sample = Js.Dict.empty()
sample->Js.Dict.set("spice-label", Js.Json.string("sample"))
sample->Js.Dict.set("spice-value", Js.Json.number(1.0))
let sampleJson = sample->Js.Json.object_

let sampleRecord: t = {
  label: "sample",
  value: 1,
}


let encoded = sampleRecord->Records.t_encode // sampleJson

let decoded = sampleJson->Records.t_decode // Belt.Result.Ok(sampleRecord)

Basically I just copy-and-paste the demo, there’s an error:

npm run start                                                                                                                                  (master)rescript-project-template

> rescript-project-template@0.0.1 start
> rescript build -w

>>>> Start compiling
Staled output removed
rescript: [2/2] src/r101.cmj
FAILED: src/r101.cmj

  We've found a bug for you!
  /Users/hellotli/Downloads/todel/rescript-project-template/src/r101.res:18:29-44

  16 │
  17 │
  18 │ let encoded = sampleRecord->Records.t_encode // sampleJson
  19 │
  20 │ let decoded = sampleJson->Records.t_decode // Belt.Result.Ok(sampleReco
     │ rd)

  The module or file Records can't be found.
  - If it's a third-party dependency:
    - Did you list it in bsconfig.json?
    - Did you run `rescript build` instead of `rescript build -with-deps`
      (latter builds third-parties)?
  - Did you include the file's directory in bsconfig.json?

FAILED: cannot make progress due to previous errors.
>>>> Finish compiling(exit: 1)

OOPS, I need a so called Records here, after went through the code base, I found a Records.res in

I download the file into src folder, and got another error

Would you try with it?

let encoded = sampleRecord->t_encode
let decoded = sampleRecord->t_decode

It passed the compiling right now, but I still can’t figure out how to infer the ReScript type from a JSON string?

As I understand, we still need to define the type first, as:

@spice
type t = {
  @spice.key("spice-label") label: string,
  @spice.key("spice-value") value: int,
}

@spice
type t1 = {
  label: string,
  value: int,
}

For example, I have a string from http response as {"name":"hello", "candidates":[1,2,3,4]} , how to get it?

decco and ppx_spice are just ppx which generates the encode and decode fn automatically based on the given type. So if you want to parse a json data to the typed data, you need to define the type first.

@spice
type t = {
  name: string,
  candidates: array<int>
}

Make sure the values of name and candidates are not nullable, if it is you need to define them as optional.