Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonMatch selected parts of data
Matching checks only the values relevant to a test. Unmentioned fields and items can vary without breaking it.
open Reified
open Reified.DataDSL
let actual =
data [
"customer" => [
"id" => "c-123"
"name" => "Ada"
"address" => [ "city" => "Adelaide"; "postcode" => 5000 ]
]
"roles" => [ "author"; "billing"; "admin" ]
"timeline" => [ "created"; "checked"; "activated" ]
"events" => [ [ "id" => "e-1" ]; [ "id" => "e-2" ] ]
"values" => [ 1; 2; 3 ]
"total" => 19.95m
]
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
actual: 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.
Check selected paths
Use at and absent when only a few observations matter:
actual
|> matching [
at "customer.name" "Ada"
at "customer.id" anyText
absent "error"
]
actual: 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.
anyText: DataPatternMatches any text value.
absent: string -> DataExpectationRequires a path to be absent.
Every expectation is checked against the same root. Failures accumulate instead of stopping after the first mismatch.
The example succeeds and returns unit. It does not compare any unmentioned response field.
For a result-returning assertion:
let result =
Data.tryMatch [
at "customer.name" "Grace"
absent "customer.id"
] actual
result: Result<unit,DataMismatch list>Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
absent: string -> DataExpectationRequires a path to be absent.
actual: Dataresult is Error containing two DataMismatch values: one at customer.name with the actual text "Ada", and one
at customer.id with the generated identifier that was expected to be absent.
result
// => Error [
// { ExpectationIndex = 0; Path = DataPath.parse "customer.name"; Actual = Some (Data.Text "Ada"); ... }
// { ExpectationIndex = 1; Path = DataPath.parse "customer.id"; Actual = Some (Data.Text "c-123"); ... }
// ]
result: Result<unit,DataMismatch list>Match a partial object
Use containing when related evidence should read as one shape:
actual
|> matching [
at "customer" (
containing [
"name" => "Ada"
"address" => containing [
"city" => "Adelaide"
]
])
]
actual: 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.
Extra fields are allowed. Required fields must be present and match their literal or recursive pattern.
This succeeds for an object such as {"name":"Ada","address":{"city":"Adelaide","postcode":5000},"id":"c-123"}.
Neither postcode nor id is rejected because the pattern does not mention them at their respective object levels.
Data.tryMatch [
at "customer" (containing [
"name" => "Ada"
"address" => containing [ "city" => "Adelaide" ]
])
] actual
// => Ok ()
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
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.
actual: DataChoose list semantics explicitly
actual
|> matching [
at "roles" (containingItems [ "admin"; "author" ])
at "timeline" (inOrder [ "created"; "activated" ])
at "events" (allItems (containing [ "id" => anyText ]))
at "values" (someItem anyNumber)
]
actual: 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.
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
inOrder: ^a list -> DataPatternMatches expected items as an ordered subsequence.
allItems: ^a -> DataPatternRequires every actual list item to satisfy a 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.
anyText: DataPatternMatches any text value.
someItem: ^a -> DataPatternRequires at least one actual list item to satisfy a pattern.
anyNumber: DataPatternMatches any number token.
containingItems consumes actual occurrences, so an actual item cannot satisfy two expected occurrences.
inOrder allows unrelated values between expected items. It does not reorder the actual list.
For roles = ["author","billing","admin"], containingItems [ "admin"; "author" ] succeeds regardless of order.
For timeline = ["created","checked","activated"], inOrder [ "created"; "activated" ] succeeds.
containingItems [ "admin"; "admin" ] fails when admin occurs once. inOrder [ "activated"; "created" ] fails
because those values occur in the opposite order.
Data.tryMatch [ at "roles" (containingItems [ "admin"; "author" ]) ] actual
// => Ok ()
Data.tryMatch [ at "roles" (containingItems [ "admin"; "admin" ]) ] actual
// => Error [ { Path = DataPath.parse "roles[1]"; Expected = "a matching list item"; ... } ]
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive 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
actual: DataUse a predicate for a local rule
let positiveNumber =
satisfying "a positive number token" (function
| Data.Number token -> decimal token > 0m
| _ -> false)
actual
|> matching [
at "total" positiveNumber
]
positiveNumber: DataPatternsatisfying: string -> (Data -> bool) -> DataPatternMatches an ordinary predicate and uses its description in diagnostics.
Reified.DataA portable tree for structured data. Data preserves null, text, number, Boolean, list, and object distinctions without depending on a serializer or input format. Data is a structured-value model, not a source syntax tree. It does not model whitespace, comments, source locations, or other format-specific syntax. Number values currently retain a lexical token so adapters do not narrow arbitrary-size integers, decimal precision, or exponent notation to one runtime numeric type.
NumberA number whose portable lexical token avoids narrowing it to one runtime numeric type.
token: stringdecimal: ^T -> decimalConverts the argument to System.Decimal using a direct conversion for all primitive numeric types. For strings, the input is converted using UInt64.Parse() with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type. The input value. The converted decimal. decimal "42.23" // evaluates to 42.23M decimal 0xff // evaluates to 255M decimal -10 // evaluates to -10M
(>): 'T -> 'T -> boolStructural greater-than The first parameter. The second parameter. The result of the comparison. 5 > 1 // Evaluates to true 5 > 5 // Evaluates to false (1, "a") > (1, "z") // Evaluates to false
actual: 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.
The description appears in mismatch output, so use wording that explains the failed requirement.
Data.Number "19.95" satisfies this predicate. Data.Number "0" produces a mismatch whose expected description is
a positive number token; Data.Text "19.95" also fails because the predicate requires a number shape.
Data.tryMatch [ at "total" positiveNumber ] (data [ "total" => 19.95m ])
// => Ok ()
Data.tryMatch [ at "total" positiveNumber ] (data [ "total" => 0 ])
// => Error [ { Path = DataPath.parse "total"; Expected = "a positive number token"; ... } ]
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
positiveNumber: DataPatterndata: 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.
Complete matching vocabulary
| Form | What it accepts |
|---|---|
at path pattern |
A present value at the path that satisfies the pattern. |
absent path |
No value at the path. |
a literal such as "Ada" |
That exact value. |
exactly value |
An explicit exact recursive pattern. |
containing fields |
An object with at least the listed matching fields. |
containingItems patterns |
A list containing each pattern in any order; occurrences are consumed once. |
inOrder patterns |
A list containing the patterns as an ordered subsequence. |
allItems pattern |
A list where every item matches. |
someItem pattern |
A list where at least one item matches. |
any |
Any present value. |
anyText |
Any text value. |
anyNumber |
Any number token. |
oneOf patterns |
A value matching at least one alternative. |
satisfying description predicate |
A value accepted by a custom predicate. |
Data.tryMatch [
at "customer.name" (oneOf [ exactly "Ada"; exactly "Grace" ])
at "customer.id" anyText
at "values" (allItems anyNumber)
] actual
// => Ok ()
Reified.DataModuletryMatch: DataExpectation list -> Data -> Result<unit,DataMismatch list>Checks path-based expectations and accumulates structured mismatches. Data.tryMatch [ at "name" "Ada" ] (data [ "name" => "Grace" ]) // Error [ mismatch at path "name": expected "Ada", found "Grace" ]
at: string -> ^a -> DataExpectationRequires a path to contain an exact value or recursive pattern.
oneOf: DataPattern list -> DataPatternMatches when one supplied alternative matches.
exactly: ^a -> DataPatternCreates an exact recursive pattern.
anyText: DataPatternMatches any text value.
allItems: ^a -> DataPatternRequires every actual list item to satisfy a pattern.
anyNumber: DataPatternMatches any number token.
actual: Datamatching expectations actual returns unit or raises DataMatchException. Data.tryMatch expectations actual
returns Result<unit, DataMismatch list> and accumulates mismatches from every expectation.
Why patterns are not constraints
anyText, anyNumber, and satisfying look like a second, smaller copy of Constraint. They are not, and they
deliberately do not reuse it.
A pattern asks a question about a Data node: is this branch text at all, is it present, does this list contain that
item. A constraint asks a question about a typed value that has already been extracted: is this string a valid email,
is this int at least 13. Those are different questions at different stages, and the one that a test of produced JSON
needs is the first.
The package graph makes the same point. Reified.Data depends on nothing, which is what lets a test project take it
without taking a validation library. Teaching at to accept a Constraint<'value> would mean Reified.Data
depending on Reified.Constraint for a convenience that only test code wants.
When you do want a typed rule over a value inside a Data tree, parse the tree with a schema and check the model —
which is the thing the schema already does, with paths and accumulated errors. Reach for satisfying for the
in-between case: one local, structural rule that no schema owns.