Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result

Data

Maturity: Incubating

Reified.Data builds and changes structured data. Its objects, lists, text, numbers, Booleans, and null map directly to JSON, and also work for test fixtures, configuration, command-line input, form values, events, and other tree-shaped data.

Build one value, derive related cases without copying it, and test the complete result or selected fields. This avoids large JSON strings, nested constructors, and near-identical fixtures. Failures identify the path that differs.

Install

Install the package with:

dotnet add package Reified.Data

Build, change, and check one value

Verified
open Reified
open Reified.DataDSL

let baseline =
    data [
        "name" => "Ada"
        "plan" => "free"
        "address" => [
            "city" => "Adelaide"
            "postcode" => 5000
        ]
        "roles" => [ "author" ]
    ]

let request =
    baseline
    |> Data.patch [
        replace "plan" "pro"
        append "roles" "admin"
    ]
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)
val baseline: Data
val request: Data

request contains the changed plan and the additional role. baseline is unchanged.

Verified
Data.render request
// => "{ name: \"Ada\", plan: \"pro\", address: { city: \"Adelaide\", postcode: 5000 }, roles: [\"author\", \"admin\"] }"
Requires & types
Requires: open Reified (packages: Reified.Data)

Check selected parts of the result:

Verified
request
|> matching [
    at "name" "Ada"
    at "address" (containing [ "postcode" => 5000 ])
    at "roles" (containingItems [ "admin" ])
    absent "error"
]
// succeeds
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)

If an expectation fails, matching raises DataMatchException with the mismatched path and values. Extra fields and the additional author role are allowed because these patterns check only the values named here.

Basic syntax

Use data to build a Data value. Fields declared with name => value make an object; ordinary values in the same list position make a Data.List instead.

Verified
open Reified
open Reified.DataDSL

let person = data [ "name" => "Ada"; "active" => true ]
let roles = [ "author"; "admin" ]
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)
val person: Data
val roles: string list

Use ?=> for an optional field. Some value includes the field; None leaves the field out altogether. Use name => nil when the field must be present with a null value.

Verified
let nickname : string option = None
let account = data [ "nickname" ?=> nickname; "deletedAt" => nil ]

Data.render account
// => "{ deletedAt: null }"
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)
val nickname: string option
val account: Data

Build data with ordinary F# control flow

data takes an F# list of fields, so the whole list-expression vocabulary is available inside it. yield! splices another object's fields, if adds a field under a condition, and for generates fields from a sequence:

Verified
let common = data [ "tenant" => "acme"; "region" => "au" ]
let names = [ "ada"; "grace" ]

let event =
    data [
        "kind" => "example"
        yield! fields common
        for name in names do
            $"user-{name}" => name
    ]
Requires & types
Requires: open Reified.DataDSL (packages: Reified.Data)
val common: Data
val names: string list
val event: Data

Fields keep yield order, including conditional and generated fields; the same forms build lists. The complete example, with conditional fields and rendered output, is in Declare, render, and edit data.

Learn and solve tasks