I would like to model the a record field contactPerson of type string.
It can be absent from the record, so one would think to use option<string>, however, if the value is absent it will be undefined which is problematic to store in my external system.
type Info = {
contactPerson: option<string> // actual string or undefined
}
So, I need to model it that is would either be an actual non empty string value or null.
// in js
let a = { contactPerson: "Foo" }
let b = { contactPerson: null }
// invalid! for my use case
let c = { contactPerson: undefined }
Alternatively, you can use rescript-schema to leverage data transformation when interacting with external systems:
type info = {
contractPerson: option<string>,
}
let infoSchema = S.object(s => {
contractPerson: s.field("contract_person", S.null(S.string))
})
let data = {
contractPerson: None,
}->S.serializeOrRaiseWith(infoSchema)
Console.log(data) // { contract_person: null }
Besides the option → null automatic conversion, you can notice in the example that you can also transform field names. And there are many more features you can find in the docs.
Also, there’s a ppx mode:
@schema
type info = {
@as("contract_person")
contractPerson: @s.null option<string>,
}
let data = {
contractPerson: None,
}->S.serializeOrRaiseWith(infoSchema)
Console.log(data) // { contract_person: null }
Off topic because I don’t think non-empty string was what you were after literally, but with v11 and unboxed variants, you can easily model a non empty string in the type system:
@unboxed type str = | @as("") EmptyString | String(string)
let print = s =>
switch s {
| EmptyString => "empty!"
| String(str) => str
}
Console.log(("" :> str)->print)
Console.log(("Hello" :> str)->print)
This doesn’t make much sense of course because you could just as easily match on an empty string directly, but still a fun little effect from untagged variants.