When @tag is provided, constructor without payload still compiles to string instead of object

Rust’s Serde serializes enums by consistently respecting tag regardless of constructor payload:

#[derive(Serialize)]
#[serde(tag = "type")]
enum X {
  A { index: usize }
  B
}

X::A { index: 1 }
X::B

would be serialized to:

{
  "type": "A",
  "index": 1
}

{
  "type": "B"
}

IMO, ReScript should follow the same pattern.

The intended semantics of @tag is to only modify the tag for cases with payloads.
It’s not intended to introduce an entirely new representation with tags everywhere.

2 Likes

For anyone, who needs serde-like behavior, the workaround is:

@tag("type")
type t =
  | A({index: int})
  | B(unit)

let a = A({index: 1})
let b = B(())
var a = {
  type: "A",
  index: 1
};

var b = {
  type: "B",
  _0: undefined
};
3 Likes

You can also do it with an optional “dummy” field in an inline record, that you then just never supply:

@tag("type")
type t =
  | A({index: int})
  | B({__dummy?: unit})

let a = A({index: 1})
let b = B({})
var a = {
  type: "A",
  index: 1
};

var b = {
  type: "B"
};

That omits the _0 payload.

We’ve been discussing allowing empty inline records, which would make expressing what you’re after possible in an idiomatic way without resorting to “hacks”. In that scenario, you’d just define B as B({}) and it’d be represented as you’d expect, with no additional cruft.

3 Likes

Thanks @zth, I’m not concerned with _0 in output tbh. Just wanted to skip deserialization without changing types, so unit works.

1 Like