Forgive the primitiveness of my question. I'm exploring Rescript and I'd like to open my package.json file into a typed datastore.
I've made some progress, but I need some direction:
@module("fs")
external readFileSync: (
~name: string,
[#utf8],
) => string = "readFileSync"
type script = {
name: string,
value: string
}
type packageData = {
name: string,
version: string,
scripts: array<script>,
keywords: array<string>,
author: option<string>,
license: string,
dependencies: array<(string, string)>,
}
let file = readFileSync(~name="package.json", #utf8)
Console.log("File: " ++ file);
@scope("JSON") @val
external parseIntoMyData: string => packageData = "parse"
let result = parseIntoMyData(file)
Console.log("deps: " ++ result.scripts[0].name)
But the last line: 30 │ Console.log("deps: " ++ result.scripts[0].name)
gives an error:
This has type: option<script>
But it's expected to have type: packageData
Here is the package.json content:
{
"name": "cli",
"version": "0.0.0",
"scripts": {
"res:build": "rescript",
"res:clean": "rescript clean",
"res:dev": "rescript -w"
},
"keywords": [
"rescript"
],
"author": "",
"license": "MIT",
"dependencies": {
"@rescript/core": "1.4.0",
"rescript": "11.1.1"
}
}
There are a few things going on here.
The first thing is that you defined multiple records that share the same fields (in this case, the field name
), so the compiler thinks you're referring to the latest record you've defined (packageData
) when you actually want to refer to script
. The best solution here (and the most idiomatic) is to namespace those types by placing them inside modules:
module Script = {
type t = {
name: string,
value: string,
}
}
module PackageData = {
type t = {
name: string,
version: string,
scripts: dict<string>,
keywords: array<string>,
author: option<string>,
license: string,
dependencies: array<(string, string)>,
}
}
Then you're facing another issue, you're trying to get an element from an array, you might be used to think that it would return an element but rescript is a pretty safe language and the compiler takes into account the fact that there might be no element at this index so it returns an option of element, in this case, which forces you to handle both cases. For example here you could do:
Console.log(switch result.scripts[0] {
| None => "no script defined"
| Some({name}) => `first script: ${name}`
})
But then you'd realize that scripts
in package.json is not an array of script but actually a string dict, so you should model it accordingly.
See working example in the playground
But to be honest, if you're planning to parse things, I'd actually advise you to use a serializer/deserializer library like rescript-schema or ppx-spice which will allow you to gracefully handle the cases where the data doesn't follow the expected shape.
I hope it answers your question and that you'll enjoy learning rescript! :)