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 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 }
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