And this type to represent in ReScript the info from the JSON:
type thing = A | B(string)
Here’s some bs-json code to parse the JSON into thing values. In a strongly typed language I do expect this kind of code to be more complicated than you might expect coming from languages like JavaScript, but can anybody suggest any improvements?
type t = {hello: thing}
let decodeA = json => {
if json == Js.Json.stringArray(["A"]) {
A
} else {
raise(Json.Decode.DecodeError("Not an A"))
}
}
let decodeB = {
open Json.Decode
pair(string, string) |> map(((code, value)) =>
if code == "B" {
B(value)
} else {
raise(DecodeError("Not a B"))
}
)
}
let decodeThing = {
open Json.Decode
either(decodeA, decodeB)
}
let decodeHellow = json => {
open Json.Decode
{
hello: json |> field("hello", decodeThing),
}
}
let parseHellow = json => json |> Json.parseOrRaise |> decodeHellow
let x = parseHellow("{\"hello\": [\"B\", \"world\"]}")
Js.log2("parsed value", switch(x.hello) {| A => "A" | B(value) => "B " ++ value})
The input JSON here comes from OCaml values of essentially the same type (so there’s not much chance of them being invalid):
type thing = A | B of string [@@deriving yojson]
type t = { hello : thing } [@@deriving yojson]
let hello = { hello = B "world" }
let jsony = yojson_of_t hello
let json = Yojson.Safe.to_string jsony
This actually seems to be a no-op. I think it tries to do a best-effort ‘patch’ to make the JSON data ‘look’ like ReScript’s expected representation. But ReScript’s representation is not compatible with ppx_deriving_yojson representation. Here’s ReScript:
// Input ReScript:
let test1 = {hello: A}
let test2 = {hello: B("world")}
// Output JS:
var test1 = {
hello: /* A */0
};
var test2 = {
hello: /* B */{
_0: "world"
}
};
Definitely this mismatch would cause runtime type errors. I think it’s safer to stick with bs-json.
It looks like patch and serializeExn are both mainly (only?) concerned with converting ReScript None values into a JSON representation ({RE_PRIVATE_NONE : true}), since undefined isn’t allowed in JSON.
That is vastly better and now seems absurdly obvious, thanks.
I found I had to do this:
let decodeThing2 = json => {
open Json.Decode
switch json |> array(string) {
| ["A"] => A
| ["B", str] => B(str)
| _ => raise(DecodeError("Expected A | B(string"))
| exception DecodeError(_) => raise(DecodeError("Expected A | B(string"))
}
}
or else I get:
Exception patterns must be at the top level of a match case.
Obviously I could pull out the raise into a function but I wonder if I’m missing something about the syntax and I could use fall-through somehow as your suggested code does? I don’t know what “top level” refers to in the error: if I try fall-through with the exception listed first, I get the same error:
| exception DecodeError(_)
| _ => raise(DecodeError("Expected A | B(string"))
Hmm, that’s weird, I’m using exactly that pattern in a newer version of OCaml. Perhaps ReScript’s version doesn’t support it yet. Actually come to think of it, since we’re just rethrowing the exception here, it might not even be worth catching it at all.