Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultData
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
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"
]
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
baseline: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
request: Data(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.DataModulepatch: DataEdit list -> Data -> DataApplies edits atomically or raises DataPatchException. Data.data [ Data.assoc "name" "Ada" ] |> Data.patch [ DataEdit.replace "name" "Grace" ] // Data.data [ Data.assoc "name" "Grace" ]
replace: string -> ^a -> DataEditReplaces an existing value.
append: string -> ^a -> DataEditAppends an item to an existing list.
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)val baseline: Dataval request: Datarequest contains the changed plan and the additional role. baseline is unchanged.
Data.render request
// => "{ name: \"Ada\", plan: \"pro\", address: { city: \"Adelaide\", postcode: 5000 }, roles: [\"author\", \"admin\"] }"
Reified.DataModulerender: Data -> stringRenders structured data in a compact, human-readable form. Data.render (data [ "name" => "Ada"; "active" => true ]) // { name: "Ada", active: true }
request: DataRequires & types
Requires: open Reified (packages: Reified.Data)Check selected parts of the result:
request
|> matching [
at "name" "Ada"
at "address" (containing [ "postcode" => 5000 ])
at "roles" (containingItems [ "admin" ])
absent "error"
]
// succeeds
request: Data(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
matching: DataExpectation list -> Data -> unitChecks expectations or raises DataMatchException. open Reified.DataDSL let actual = data [ "user" => data [ "name" => "Ada" ] ] matching [ at "user.name" "Ada"; absent "error" ] actual // returns unit when both expectations hold; otherwise raises DataMatchException
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
containing: DataField list -> DataPatternCreates a partial object pattern from required fields. Data.tryMatch [ at "" (containing [ "id" => 42 ]) ] (data [ "id" => 42; "extra" => true ]) // Ok ()
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
containingItems: ^a list -> DataPatternMatches expected items as an unordered consumed subset. open Reified.DataDSL let actual = data [ "items" => [ "Ada"; "Grace"; "Mallory" ] ] Data.tryMatch [ at "items" (containingItems [ "Ada"; "Grace" ]) ] actual // Ok () when both values occur, in either order
absent: string -> DataExpectationRequires a path to be absent.
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.
open Reified
open Reified.DataDSL
let person = data [ "name" => "Ada"; "active" => true ]
let roles = [ "author"; "admin" ]
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
person: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
roles: string listRequires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val person: Dataval roles: string listUse ?=> 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.
let nickname : string option = None
let account = data [ "nickname" ?=> nickname; "deletedAt" => nil ]
Data.render account
// => "{ deletedAt: null }"
nickname: string optionstringAn abbreviation for the CLI type . Basic Types
optionThe type of optional values. When used from other CLI languages the empty option is the null value. Use the constructors Some and None to create values of this type. Use the values in the Option module to manipulate values of this type, or pattern match against the values directly. 'None' values will appear as the value null to other CLI languages. Instance methods on this type will appear as static methods to other CLI languages due to the use of null as a value representation. Options
NoneThe representation of "No value"
account: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(?=>): string -> ^value option -> DataFieldAssociates a field name with an optional exact value, omitting None.
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
nil: DataAn explicit structured null used by literals and edits.
Reified.DataModulerender: Data -> stringRenders structured data in a compact, human-readable form. Data.render (data [ "name" => "Ada"; "active" => true ]) // { name: "Ada", active: true }
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)val nickname: string optionval account: DataBuild 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:
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
]
common: Datadata: DataField list -> DataBuilds an object from ordered field instructions. data [ "name" => "Ada"; "active" => true ] // Data.Object [ "name", Data.Text "Ada"; "active", Data.Bool true ]
(=>): string -> ^value -> DataFieldAssociates a field name with an exact value or recursive data pattern.
names: string listevent: Datafields: Data -> DataField listReturns exact field instructions for spreading an existing object literal.
name: stringRequires & types
Requires: open Reified.DataDSL (packages: Reified.Data)val common: Dataval names: string listval event: DataFields 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.