Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.JsonConvert data and parse JSON
Data is a tree of objects, lists, text, numbers, Booleans, and null. Conversion functions copy other representations
into that tree. Extraction and rendering functions take values back out.
open Reified
open Reified.DataDSL
ReifiedReified.DataDSLConcise opt-in syntax for literals, immutable edits, cases, and matching.
Parse JSON text portably
Install Reified.Schema.Json when JSON text must be parsed the same way on .NET and Fable. Json.parseData reads one
complete JSON value into a Data tree while preserving object field order, duplicate field names, and number-token
spelling.
open Reified.Schema.Json
let value = Json.parseData """{"amount":1.20e+3,"active":true}"""
value
// => Data.Object [
// "amount", Data.Number "1.20e+3"
// "active", Data.Bool true
// ]
ReifiedSchemaJsonvalue: DataReified.Schema.Json.JsonFunctions for compiling and running JSON codecs over built model schemas.
parseData: string -> DataParses one JSON value into source-neutral structured data. Preserves object field order, duplicate field names, and the original spelling of number tokens. This parser is available on .NET and Fable. It does not apply a model schema; use deserialize with a compiled codec when decoding directly to a schema-described model. Json.parseData "{\"name\":\"Ada\"}" // Data.Object [ "name", Data.Text "Ada" ] Thrown when is null. Thrown when the input is not one complete JSON value.
Invalid JSON raises JsonCodecException. parseData does not decode fields into application-specific record types;
it only converts JSON syntax into the corresponding Data cases.
Data.Json.render and Data.Json.renderIndented produce JSON text. Data.render and Data.renderIndented instead
produce a concise human-readable display.
Data.Json.render value
// => "{\"amount\":1.20e+3,\"active\":true}"
Reified.DataModulerender: Data -> stringRenders compact deterministic JSON. Data.Json.render (Data.Object [ "name", Data.Text "Ada" ]) // {"name":"Ada"}
Reified.DataModule.JsonDeterministic JSON rendering for structured values.
value: DataUse the native .NET parser
On .NET 8+, use Data.ofJsonElement or Data.ofJsonDocument when JSON has already been parsed with
System.Text.Json. These conversion functions are intentionally .NET-only.
use document = System.Text.Json.JsonDocument.Parse("""{"name":"Ada"}""")
let value = Data.ofJsonDocument document
Data.Json.render value
// => "{\"name\":\"Ada\"}"The returned Data is a copy and remains usable after the document is disposed.
Use the native JavaScript parser under Fable
Under Fable, pass the result of the host's JSON.parse to Data.ofJsonValue:
open Fable.Core
let value = JS.JSON.parse """{"name":"Ada","active":true}""" |> Data.ofJsonValueNative JSON parsing is convenient when its normal JavaScript semantics are acceptable. It discards duplicate object
fields and converts numbers to JavaScript numbers, so it cannot preserve the original number-token spelling. Use
Json.parseData from Reified.Schema.Json when those distinctions matter.
Convert F# and .NET values
| Input | Function | Result |
|---|---|---|
Map<string, Data> |
Data.objectOfMap |
Object fields ordered by map key. |
(string * Data) list |
Data.objectOfList |
Object fields in list order, including duplicates. |
Map<string, string> |
Data.ofMap |
Object of text or null values. |
IDictionary<string, string> |
Data.ofDictionary |
Object of text or null values. |
seq<string * string> |
Data.ofNameValues |
Repeated names become lists. |
NameValueCollection |
Data.ofNameValueCollection |
Repeated names become lists. |
| command-line arguments | Data.ofCliArgs |
Parsed option names and their supplied values. |
| flattened configuration pairs | Data.ofConfiguration |
Nested objects and lists from colon-separated keys. |
| configuration key/value pairs | Data.ofConfigurationPairs |
The .NET configuration form, including null values. |
The three source adapters below differ in how they treat repetition and nesting, so each one's rules are worth reading before you rely on them.
Command-line arguments
Data.ofCliArgs takes seq<string> — normally the arguments handed to main — and always returns a Data.Object.
Data.ofCliArgs
[ "--name"; "Ada"
"--tag=fsharp"; "--tag"; "validation"
"--active"
"--no-archived"
"import.csv"
"--"; "--literal" ]
// => Data.Object [
// "_", Data.List [ Data.Text "import.csv"; Data.Text "--literal" ]
// "active", Data.Text "true"
// "archived", Data.Text "false"
// "name", Data.Text "Ada"
// "tag", Data.List [ Data.Text "fsharp"; Data.Text "validation" ]
// ]
Reified.DataModuleofCliArgs: string seq -> DataBuilds structured data from command-line arguments.
The rules:
--name value,--name=value,-n value, and-n=valueall set the fieldname(orn) to the value.- The following token is taken as the value only when it does not start with
-. Otherwise the option is a flag and gets the text"true". --no-namesets the fieldnameto the text"false".- A repeated option collects its values into a
Data.List, in the order they appeared. - Anything that is not an option is positional, and positionals are collected under the field
_. --ends option parsing: every remaining token is positional, even if it starts with a dash.- Fields come out ordered by name, not in command-line order. Positional order is preserved within
_.
Every CLI leaf is text, because that is all a command line carries. Data.ofCliArgs identifies structure; it never
decides that "42" is a number or "true" is a Boolean. That decision belongs to the schema that parses the tree.
Configuration keys
Data.ofConfiguration takes flattened seq<string * string> and splits each key on : to rebuild the nesting. A
segment that is a non-negative integer is an index, so those siblings become a Data.List.
Data.ofConfiguration
[ "displayName", "Ada"
"contacts:0:value", "ada@example.com"
"contacts:1:value", "+61 400 000 000"
"features:email", "true" ]
// => Data.Object [
// "contacts", Data.List [
// Data.Object [ "value", Data.Text "ada@example.com" ]
// Data.Object [ "value", Data.Text "+61 400 000 000" ]
// ]
// "displayName", Data.Text "Ada"
// "features", Data.Object [ "email", Data.Text "true" ]
// ]
Reified.DataModuleofConfiguration: (string * string) seq -> DataBuilds structured data from flattened configuration keys.
Leaves stay text, or Data.Null when the value is null. Pairs are applied in order and later values win, which matches
.NET configuration layering:
Data.ofConfiguration [ "name", "default"; "name", "Ada" ]
// => Data.Object [ "name", Data.Text "Ada" ]
Data.ofConfiguration [ "a", "1"; "a:b", "2" ]
// => Data.Object [ "a", Data.Object [ "b", Data.Text "2" ] ]
Reified.DataModuleofConfiguration: (string * string) seq -> DataBuilds structured data from flattened configuration keys.
Repetition never builds a list here — only indexed segments do. One exception protects sections: a null value does not
overwrite an existing section, because IConfiguration.AsEnumerable() emits every section key with a null value
alongside that section's children.
Data.ofConfigurationPairs has exactly these semantics but accepts KeyValuePair<string, string>, which is the shape
IConfiguration.AsEnumerable() returns, so it can be piped straight in.
Name/value pairs
Data.ofNameValues (and Data.ofNameValueCollection) repeat differently from configuration: a repeated name is
the list-building mechanism, because that is how query strings and form posts carry multiple values.
Data.ofNameValues [ "tag", "fsharp"; "tag", "validation"; "name", "Ada" ]
// => Data.Object [
// "tag", Data.List [ Data.Text "fsharp"; Data.Text "validation" ]
// "name", Data.Text "Ada"
// ]
Reified.DataModuleofNameValues: (string * string) seq -> DataBuilds object-shaped data from name and value pairs.
Where this leads
Every adapter produces the same source-neutral Data. That tree is a shape, not a domain model: nothing has been
typed, checked, or named in your vocabulary yet. Schema.parse takes it the rest of the way, converting leaves to
typed values, applying rules, and reporting failures against field paths.
Data.ofCliArgs argv
|> Schema.parse Registration.schemaStart from the smallest schema if you have not declared one yet.
Read values from Data
Use paths to retrieve nested data. tryFindPath returns None for a missing path; lookupPath returns Data.Null.
The parsed-path equivalents are tryFind and lookup.
let nestedValue = data [ "customer" => [ "name" => "Ada" ] ]
Data.tryFindPath "customer.name" nestedValue
// => Some (Data.Text "Ada")
Data.lookupPath "customer.missing" nestedValue
// => Data.Null
nestedValue: 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.
Reified.DataModuletryFindPath: string -> Data -> Data optionAttempts to parse a path and find its value.
lookupPath: string -> Data -> DataParses a path and finds its value or returns Null.
Shape-specific extractors return Some only for the named case:
Data.tryText (Data.Text "Ada") // => Some "Ada"
Data.tryBool (Data.Bool true) // => Some true
Data.tryNumberToken (Data.Number "1.20") // => Some "1.20"
Data.tryList (Data.List []) // => Some []
Data.tryObject (Data.Object []) // => Some []
Reified.DataModuletryText: Data -> string optionAttempts to extract text from one structured value. Data.tryText (Data.Text "Ada") // Some "Ada"
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.
TextA text value.
tryBool: Data -> bool optionAttempts to extract a Boolean from one structured value. Data.tryBool (Data.Bool true) // Some true
BoolA Boolean value.
tryNumberToken: Data -> string optionAttempts to extract the preserved token from one number value. Data.tryNumberToken (Data.Number "1e3") // Some "1e3"
NumberA number whose portable lexical token avoids narrowing it to one runtime numeric type.
tryList: Data -> Data list optionAttempts to extract items from one list value. Data.tryList (Data.List []) // Some []
ListAn ordered collection of structured values.
tryObject: Data -> (string * Data) list optionAttempts to extract ordered fields from one object value. Data.tryObject (Data.Object []) // Some []
ObjectAn ordered collection of named structured values.
tryRedisplay, redisplay, and their At and Path forms turn scalar values back into form-style text. Use JSON
rendering when the output must remain structured JSON.