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

What Data represents

Data represents structured values with six cases. The model maps directly to JSON and also works for fixtures, configuration, command-line input, form values, events, and other tree-shaped data.

Data.render uses a compact display with unquoted ordinary field names and JSON quoting and escaping for text. For scalars it reads the same as JSON output; for Data.Object it does not, since JSON requires quoted field names and Data.render does not produce them. Use Data.Json.render when the result must be valid JSON. See Render for people.

Verified
type Data =
    | Null
    | Text of string
    | Number of token: string
    | Bool of bool
    | List of Data list
    | Object of (string * Data) list

Data.Null

Null is a present value with no scalar content.

Verified
open Reified

Data.render Data.Null
// => "null"
Requires & types
Requires: open Reified (packages: Reified.Data)

A null field is different from an absent field. In literal syntax, "value" => nil includes a null field, while "value" ?=> None omits the field.

Data.Text

Text stores a .NET string and renders it as a quoted string with the required escaping.

Verified
Data.render (Data.Text "Ada\nLovelace")
// => "\"Ada\\nLovelace\""
Requires & types
Requires: open Reified (packages: Reified.Data)

A null .NET string converts to Data.Null; it does not become Data.Text null.

Data.Number

Number stores the number as a token rather than converting every number to one CLR numeric type. This retains large integers, decimal precision, trailing zeros, and exponent notation.

Verified
Data.render (Data.Number "1.2300e+4")
// => "1.2300e+4"
Requires & types
Requires: open Reified (packages: Reified.Data)

render reproduces the token without reformatting. The token is visible to exact comparison: 1, 1.0, and 1e0 are different. Use the num function rather than the union case when accepting text, because num checks that the token is a valid JSON number.

The Number union case does not validate its token, so Data.Number "not a number" constructs a value directly. Data.render and Data.Json.render both emit the token verbatim; for an invalid token that produces output that is not valid JSON. Always go through Data.number/Data.num, or the JSON parser, when the token came from input you did not already validate.

See Numbers for conversion rules and examples.

Data.Bool

Bool stores true or false.

Verified
Data.render (Data.Bool true)
// => "true"
Requires & types
Requires: open Reified (packages: Reified.Data)

Data.List

List stores ordered Data values. Order and repeated values are preserved.

Verified
Data.render (Data.List [ Data.Text "author"; Data.Text "admin" ])
// => "[\"author\", \"admin\"]"
Requires & types
Requires: open Reified (packages: Reified.Data)

Exact comparison checks every item and its position. Matching can instead check an ordered subsequence, an unordered subset, every item, or at least one item.

Data.Object

Object stores an ordered list of name/value pairs. It preserves declaration order and duplicate field names.

Verified
let value =
    Data.Object [
        "name", Data.Text "Ada"
        "name", Data.Text "Grace"
    ]

Data.render value
// => "{ name: \"Ada\", name: \"Grace\" }"
Requires & types
Requires: open Reified (packages: Reified.Data)
val value: Data

Path lookup and edits select the last field when a name is repeated. Exact comparison observes every field, including duplicates and order. Partial object matching consumes matching duplicate fields one occurrence at a time.

Verified
open Reified.DataDSL

let repeated =
    Data.Object [
        "name", Data.Text "Ada"
        "name", Data.Text "Grace"
    ]

Data.lookupPath "name" repeated
// => Data.Text "Grace"   — a path inspects the last occurrence

Data.tryMatch [ at "" (containing [ "name" => "Ada" ]) ] repeated
// => Ok ()              — containing matches the first occurrence, consuming it
Requires & types
Requires: open Reified; open Reified.DataDSL (packages: Reified.Data)
val repeated: Data

Paths

Fields and list items are addressed by a separate path type. DataPath identifies nested fields and zero-based list indexes. String paths use dots for ordinary field names, brackets for indexes, and quoted brackets for names containing punctuation.

Verified
DataPath.parse "customer.roles[1]"
// => [ DataPathSegment.Name "customer"; DataPathSegment.Name "roles"; DataPathSegment.Index 1 ]

DataPath.parse "metadata[\"build.version\"]"
// => [ DataPathSegment.Name "metadata"; DataPathSegment.Name "build.version" ]
Requires & types
Requires: open Reified (packages: Reified.Data)

The empty path "" addresses the root value.