Model string value from external system

Hello,

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 }

How would I do this in ReScript?

If you explicitly need it to be null or a string and not undefined, you can use Null.t which is defined as

@unboxed
type t<'a> =
  | Value('a)
  | @as(null) Null

(as opposed to Nullable.t which is the same but also with an Undefined case)

type info = {contactPerson: Null.t<string>}

let a = { contactPerson: Value("Foo") }
let b = { contactPerson: Null }
3 Likes

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 optionnull 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 }
5 Likes

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.

2 Likes