CkDictionary
Job: read one typed value out of a dictionary-shaped bag — a deserialized JSON payload, a database row turned into a dictionary, a config map — in a single call.
After CkPayload turns wire text into a bag of keys and values, this class
gets the values out: CkDictionary.Int32(dictionary: payload, key: "age") looks the key up
and converts the value in one step, exactly as if you had done the lookup yourself and
handed the result to the right CommonKit reader. That makes it step 2 of
the pipeline. Nothing here throws: a missing dictionary, a missing key, a
JSON null or a value that cannot be read all answer the method's fallback — 0, false,
"", an empty collection, or whatever you pass as fallback. Only Value answers null,
because for the raw escape hatch null honestly means "nothing there".
Quick look
using System.Text.Json;
using SafeCoder.CommonKit;
string json = """{ "name": "Mary", "age": "42.7", "vip": "yes", "tags": ["a", "b"] }""";
var payload = JsonSerializer.Deserialize<Dictionary<string, object>>(json);
CkDictionary.String(dictionary: payload, key: "name"); // -> "Mary"
CkDictionary.Int32(dictionary: payload, key: "age"); // -> 43 ("42.7" rounds)
CkDictionary.Bool(dictionary: payload, key: "vip"); // -> true
CkDictionary.Strings(dictionary: payload, key: "tags"); // -> ["a", "b"]
CkDictionary.Int32(
dictionary: payload,
key: "score",
fallback: -1
); // -> -1 (no such key)
CkDictionary.At(payload, "customer.address.zip"); // -> a value down a dotted path
CkDictionary.MissingKeys(payload, "name", "email"); // -> ["email"]
What counts as a bag
The dictionary parameter is object? on purpose — you can hand it any shape with string
keys:
- a
Dictionary<string, ...>holding any value type, including theDictionary<string, object>thatJsonSerializer.Deserializeproduces; - a read-only or old-style non-generic dictionary;
- a list of key–value pairs;
- a
JsonElementorJsonDocumentholding a JSON object, straight fromJsonDocument.Parse.
Values that are JsonElements unwrap automatically: JSON text becomes a .NET string, JSON
true/false a real boolean, and a whole JSON number arrives as a long (JSON has one
number type, so a safe wide one is picked). You never deal with JsonElement yourself
unless you want to.
Keys are matched exactly, and JSON keys are case-sensitive — "name" does not find
"Name". One kindness: when your key has stray spaces around it, it is tried once more
trimmed.
var row = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "id": 7 }""");
CkDictionary.Int32(dictionary: row, key: " id "); // -> 7 (your key, trimmed and retried)
Methods
String
One entry as clean text.
string String(
object? dictionary = null,
string? key = null,
string? fallback = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
string? |
null |
Backup text when the entry is missing or unreadable; it is cleaned the same way, and empty string stands in when none was given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry holds one readable value | The clean text: "Mary Smith" |
The clean text: "Mary Smith" |
| Rejected: missing entry, JSON null, no usable text, or an object or a list | The cleaned fallback; empty string when none was given |
Throws CkDictionaryException — carries Input, Key and Reason |
A boolean comes back as "True"/"False" — the framework's spelling, not JSON's.
String reads one entry as tidy text: a string value goes through CkText.String (markup
stripped, whitespace collapsed), and a number, boolean, date or GUID is written with the
library's own invariant writers — so a true becomes "True", the framework's spelling, a
DateOnly becomes "2026-08-31" (CkDate.Iso8601) and a DateTime or
DateTimeOffset becomes "2026-08-31T14:05:00Z" (CkDateTime.Iso8601).
Missing or unreadable answers the fallback, cleaned the same way; "" when none was
given.
var person = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "name": " Mary Smith ", "qty": 12, "ok": true }""");
CkDictionary.String(dictionary: person, key: "name"); // -> "Mary Smith"
CkDictionary.String(dictionary: person, key: "qty"); // -> "12"
CkDictionary.String(dictionary: person, key: "ok"); // -> "True"
CkDictionary.String(
dictionary: person,
key: "gone",
fallback: "n/a"
); // -> "n/a"
Strings
A list of clean text.
string[] Strings(
object? dictionary = null,
string? key = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering an empty list. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a list, or one lone value | The clean items: ["red", "7", "blue"] — the empty ones dropped |
The clean items: ["red", "7", "blue"] — the empty ones dropped |
| Rejected: missing entry, JSON null, or a value that is itself a dictionary | Empty list | Throws CkDictionaryException — carries Input, Key and Reason |
A lone value still lists: "solo" answers ["solo"], never a list of characters.
Strings reads a list of entries the same way as
String, dropping the ones that came out empty — and a
single value where a list was allowed still comes back as a one-item list.
var bag = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "tags": [" red ", "", 7, "blue"], "one": "solo" }""");
CkDictionary.Strings(dictionary: bag, key: "tags"); // -> ["red", "7", "blue"] (empty item dropped)
CkDictionary.Strings(dictionary: bag, key: "one"); // -> ["solo"] (one value still lists)
CkDictionary.Strings(dictionary: bag, key: "gone"); // -> [] (no such key)
The next thirteen sections are the number readers — one method per C# number type, each
handing the found value to its CkNumber twin, so the same rules apply:
fractions round to the nearest whole number for the integer types (exact: true refuses
them instead), out-of-range and not-a-number answer the fallback, 0 unless you pick
another. Each member below only tells you the type you get and what it holds; the shared
exact and fallback moves are shown once, under
Int32.
Byte
A whole number from 0 to 255.
byte Byte(
object? dictionary = null,
string? key = null,
bool exact = false,
byte fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
byte |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted byte: 200 |
The converted byte: 200 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a byte, the smallest whole-number type — right for tiny counts that can never
go negative.
var levels = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "level": 200, "big": 300 }""");
CkDictionary.Byte(dictionary: levels, key: "level"); // -> 200
CkDictionary.Byte(dictionary: levels, key: "big"); // -> 0 (300 is out of range — the fallback)
SByte
A whole number from -128 to 127.
sbyte SByte(
object? dictionary = null,
string? key = null,
bool exact = false,
sbyte fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
sbyte |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted sbyte: -100 |
The converted sbyte: -100 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get an sbyte, the signed twin of Byte.
var steps = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "offset": -100 }""");
CkDictionary.SByte(dictionary: steps, key: "offset"); // -> -100
Int16
A whole number to about 32 thousand.
short Int16(
object? dictionary = null,
string? key = null,
bool exact = false,
short fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
short |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted short: 2024 |
The converted short: 2024 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a short, holding -32,768 to 32,767.
var when = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "year": 2024 }""");
CkDictionary.Int16(dictionary: when, key: "year"); // -> 2024
UInt16
A positive whole number to 65,535.
ushort UInt16(
object? dictionary = null,
string? key = null,
bool exact = false,
ushort fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
ushort |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted ushort: 8080 |
The converted ushort: 8080 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a ushort, holding 0 to 65,535 — the classic fit for a network port.
var net = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "port": 8080 }""");
CkDictionary.UInt16(dictionary: net, key: "port"); // -> 8080
Int32
The everyday whole number.
int Int32(
object? dictionary = null,
string? key = null,
bool exact = false,
int fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
int |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted int: 7 |
The converted int: 7 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get an int, holding about ±2.1 billion — C#'s default whole-number type, and the
one this example uses to show the family's shared exact and fallback moves.
var order = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "id": 7, "qty": "3.7", "note": "n/a" }""");
CkDictionary.Int32(dictionary: order, key: "id"); // -> 7
CkDictionary.Int32(dictionary: order, key: "qty"); // -> 4 ("3.7" rounds to nearest)
CkDictionary.Int32(
dictionary: order,
key: "qty",
exact: true
); // -> 0 (a fraction is refused)
CkDictionary.Int32(
dictionary: order,
key: "note",
fallback: -1
); // -> -1 ("n/a" is not a number)
UInt32
A positive whole number to about 4.3 billion.
uint UInt32(
object? dictionary = null,
string? key = null,
bool exact = false,
uint fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
uint |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted uint: 3000000000 |
The converted uint: 3000000000 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a uint — twice the positive room of an int, no negatives.
var stats = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "views": 3000000000 }""");
CkDictionary.UInt32(dictionary: stats, key: "views"); // -> 3000000000 (too big for int, fine for uint)
Int64
A big whole number.
long Int64(
object? dictionary = null,
string? key = null,
bool exact = false,
long fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
long |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted long: 5000000000 |
The converted long: 5000000000 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a long, holding about ±9.2 quintillion — the type whole JSON numbers already
arrive as.
var file = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "bytes": 5000000000 }""");
CkDictionary.Int64(dictionary: file, key: "bytes"); // -> 5000000000
UInt64
A big positive whole number.
ulong UInt64(
object? dictionary = null,
string? key = null,
bool exact = false,
ulong fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
ulong |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted ulong: 18446744073709551615 |
The converted ulong: 18446744073709551615 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a ulong, holding 0 to about 18.4 quintillion.
var count = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "total": "18446744073709551615" }""");
CkDictionary.UInt64(dictionary: count, key: "total"); // -> 18446744073709551615 (the very top of the range)
Int128
A huge whole number.
System.Int128 Int128(
object? dictionary = null,
string? key = null,
bool exact = false,
System.Int128 fallback = default,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
System.Int128 |
default (0) |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted System.Int128: 12345678901234567890123456789 |
The converted System.Int128: 12345678901234567890123456789 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a System.Int128, holding about ±1.7 × 10³⁸ — for values even a long cannot
hold; they usually travel as numeric text.
var big = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "n": "12345678901234567890123456789" }""");
CkDictionary.Int128(dictionary: big, key: "n"); // -> 12345678901234567890123456789
UInt128
A huge positive whole number.
System.UInt128 UInt128(
object? dictionary = null,
string? key = null,
bool exact = false,
System.UInt128 fallback = default,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
exact |
bool |
false |
false rounds a fractional value to the nearest whole number first; true accepts only whole values. |
fallback |
System.UInt128 |
default (0) |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted System.UInt128: 340282366920938463463374607431768211455 |
The converted System.UInt128: 340282366920938463463374607431768211455 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions round to nearest by default (exact: true refuses them); out of range answers the fallback, never a clamp.
You get a System.UInt128, holding 0 to about 3.4 × 10³⁸ — the unsigned twin of
Int128.
var huge = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "n": "340282366920938463463374607431768211455" }""");
CkDictionary.UInt128(dictionary: huge, key: "n"); // -> 340282366920938463463374607431768211455 (UInt128's maximum)
Single
A decimal number, about 7 digits.
float Single(
object? dictionary = null,
string? key = null,
float fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
float |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted float: 0.25f |
The converted float: 0.25f |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions are kept — the decimal readers take no exact.
You get a float, precise to about 7 significant digits (the digits that carry meaning,
counted from the first non-zero one) — fine for measurements, not for money. The three
decimal readers keep fractions, so there is no exact here.
var mix = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "ratio": "0.25" }""");
CkDictionary.Single(dictionary: mix, key: "ratio"); // -> 0.25f
Double
A decimal number, about 15 digits.
double Double(
object? dictionary = null,
string? key = null,
double fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
double |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted double: 3.14159 |
The converted double: 3.14159 |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions are kept — the decimal readers take no exact.
You get a double, precise to about 15 significant digits — C#'s default for decimal
numbers. Works like Single, just wider.
var circle = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "pi": "3.14159" }""");
CkDictionary.Double(dictionary: circle, key: "pi"); // -> 3.14159
Decimal
The exact one for money.
decimal Decimal(
object? dictionary = null,
string? key = null,
decimal fallback = 0,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
decimal |
0 |
What comes back when the entry is missing or cannot be converted. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a number, or numeric text, in range | The converted decimal: 19.99m |
The converted decimal: 19.99m |
| Rejected: missing entry, JSON null, out of range, or not a number | The fallback (default 0) |
Throws CkDictionaryException — carries Input, Key and Reason |
Fractions are kept — the decimal readers take no exact.
You get a decimal, holding 28 significant digits, exact — right for money, where
float and double would drift.
var cart = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "price": "19.99" }""");
CkDictionary.Decimal(dictionary: cart, key: "price"); // -> 19.99m
Bool
True and false, strictly.
bool Bool(
object? dictionary = null,
string? key = null,
bool fallback = false,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
bool |
false |
What comes back when the entry is missing or not a recognized boolean — an unreadable value is not read as false. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
The entry is a real boolean, a listed word, or a number that is exactly 1 or 0 |
The boolean: true |
The boolean: true |
Rejected: missing entry, JSON null, or not a boolean — "published", 2, a date |
The fallback (default false) |
Throws CkDictionaryException — carries Input, Key and Reason |
Unreadable is not false — it answers the fallback, so map your own status words with CkBool.FromText.
The value goes to CkBool, so the library's one boolean rule applies. A real
boolean passes straight through. Text is read case-insensitively from a fixed list: true,
t, yes, y, on mean true; false, f, no, n, off mean false. A number counts
only when it is exactly 1 or 0 — as a number, or as numeric text such as "1" or
"1.0". Anything else — "published", 2, a date — is not silently false: it is
unreadable, so you get the fallback (false unless you pick another).
var flags = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "a": true, "b": "YES", "c": 0, "d": "published" }""");
CkDictionary.Bool(dictionary: flags, key: "a"); // -> true
CkDictionary.Bool(dictionary: flags, key: "b"); // -> true ("YES" is on the list)
CkDictionary.Bool(dictionary: flags, key: "c"); // -> false (the number 0)
CkDictionary.Bool(dictionary: flags, key: "d"); // -> false (the fallback — not a reading)
CkDictionary.Bool(
dictionary: flags,
key: "d",
fallback: true
); // -> true (the fallback again)
When "unknown" must not become "no" — a nullable column, a consent flag — take the entry
with Value and hand it to CkBool.FromValueOrNull,
which answers null for a missing or unreadable value:
CkBool.FromValueOrNull(CkDictionary.Value(dictionary: flags, key: "c")); // -> false (said no)
CkBool.FromValueOrNull(CkDictionary.Value(dictionary: flags, key: "d")); // -> null (unreadable)
CkBool.FromValueOrNull(CkDictionary.Value(dictionary: flags, key: "e")); // -> null (no such entry)
The two date readers follow the column type: ToDateTime hands the value to
CkDateTime and answers a moment, ToDateOnly to CkDate and
answers a calendar day. Every shape those classes read works here: ISO 8601, compact
yyyyMMdd, slashed dates in your declared order, Unix timestamps, boxed date types.
assumeZone says which clock a zone-less value was written on (CkSettings.DefaultAssumeZone
behind it); zone, on ToDateOnly, says whose calendar day a moment belongs to
(CkSettings.DefaultZone behind it) — a date with no time of day is never moved by either.
SQL placeholder dates such as 1900-01-01 count as missing by default
(treatSentinelsAsMissing). Missing or unreadable answers your fallback — the year-one
minimum value when none was given, a date no real record has.
ToDateTime
A date with its time of day.
DateTime ToDateTime(
object? dictionary = null,
string? key = null,
DateOrder order = CkSettings.DefaultDateOrder,
string? format = null,
string? assumeZone = CkSettings.DefaultAssumeZone,
bool treatSentinelsAsMissing = true,
DateTime? fallback = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
order |
DateOrder |
CkSettings.DefaultDateOrder (DateOrder.MonthFirst) |
How a slashed date orders its parts. |
format |
string? |
null |
An exact .NET format the text must match, when one is declared. |
assumeZone |
string? |
CkSettings.DefaultAssumeZone (null) |
A time zone id for zone-less values, converted to UTC — see CkDateTime.ToDateTime. |
treatSentinelsAsMissing |
bool |
true |
Reads SQL placeholder dates such as 1900-01-01 as missing. |
fallback |
DateTime? |
null |
What comes back when the entry is missing or unreadable, normalized to UTC and checked the same way — a placeholder fallback such as 1900-01-01 is no fallback while treatSentinelsAsMissing is true; year one (DateTime.MinValue, UTC) when not given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry holds a readable date | The DateTime, UTC: 2023-11-14 22:13:20 |
The DateTime, UTC: 2023-11-14 22:13:20 |
| Rejected: missing entry, JSON null, a placeholder date, or unreadable text | The fallback, UTC; year one (DateTime.MinValue, UTC) when none was given or the fallback is a placeholder too |
Throws CkDictionaryException — carries Input, Key and Reason |
A JSON number is read as a Unix timestamp, never as .NET ticks.
ToDateTime reads a full DateTime — a calendar date together with a time of day.
var visit = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "since": "2024-03-01", "stamp": 1700000000 }""");
CkDictionary.ToDateTime(dictionary: visit, key: "stamp"); // -> 2023-11-14 22:13:20 UTC (Unix seconds)
CkDictionary.ToDateTime(dictionary: visit, key: "gone"); // -> DateTime.MinValue (year one — the sentinel)
ToDateOnly
A calendar date alone.
DateOnly ToDateOnly(
object? dictionary = null,
string? key = null,
DateOrder order = CkSettings.DefaultDateOrder,
string? format = null,
string? assumeZone = CkSettings.DefaultAssumeZone,
string? zone = CkSettings.DefaultZone,
bool treatSentinelsAsMissing = true,
DateOnly? fallback = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
order |
DateOrder |
CkSettings.DefaultDateOrder (DateOrder.MonthFirst) |
How a slashed date orders its parts. |
format |
string? |
null |
An exact .NET format the text must match, when one is declared. |
assumeZone |
string? |
CkSettings.DefaultAssumeZone (null) |
The time zone the value's digits are wall-clock in, when the value declares no zone of its own. null means zone-less digits count as UTC. |
zone |
string? |
CkSettings.DefaultZone (null) |
The time zone whose calendar day counts, as an IANA or Windows id; null for the UTC day. Only a value with a time of day is placed: a date alone is that day everywhere, and no zone moves it — see CkDate.ToDateOnly. |
treatSentinelsAsMissing |
bool |
true |
Reads SQL placeholder dates such as 1900-01-01 as missing. |
fallback |
DateOnly? |
null |
What comes back when the entry is missing or unreadable, checked the same way — a placeholder fallback such as 1900-01-01 is no fallback while treatSentinelsAsMissing is true; year one (DateOnly.MinValue) when not given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry holds a readable date | The DateOnly: 2024-03-01 |
The DateOnly: 2024-03-01 |
| Rejected: missing entry, JSON null, a placeholder date, or unreadable text | The fallback; year one (DateOnly.MinValue) when none was given or the fallback is a placeholder too |
Throws CkDictionaryException — carries Input, Key and Reason |
SQL placeholder dates such as 1900-01-01 count as missing unless you switch treatSentinelsAsMissing off.
Works like ToDateTime but answers a DateOnly
— a date with no time of day — and its year-one sentinel is DateOnly.MinValue. A value
that carries a time of day is placed on zone's calendar first, so a moment just after
midnight UTC is still the evening before in Chicago; a date alone is never moved.
// visit is the bag from the ToDateTime example above
CkDictionary.ToDateOnly(dictionary: visit, key: "since"); // -> 2024-03-01
CkDictionary.ToDateOnly(dictionary: visit, key: "stamp"); // -> 2023-11-14 (the UTC day of the Unix seconds)
CkDictionary.ToDateOnly(
dictionary: visit,
key: "stamp",
zone: "Asia/Tokyo"
); // -> 2023-11-15 (22:13 UTC is 07:13 the next morning in Tokyo)
ToGuid
Identifiers out.
Guid ToGuid(
object? dictionary = null,
string? key = null,
bool treatZerosAsMissing = true,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
treatZerosAsMissing |
bool |
true |
Reads an all-zeros UUID in the data as missing, as CkUuid does. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering Guid.Empty. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
The entry is a UUID in any shape CkUuid reads |
The Guid: 0f8fad5b-d9cb-469f-a165-70867728950e |
The Guid: 0f8fad5b-d9cb-469f-a165-70867728950e |
| Rejected: missing entry, JSON null, an all-zeros UUID, or unreadable text | Guid.Empty |
Throws CkDictionaryException — carries Input, Key and Reason |
No substitute fallback for an identity — a rejected id is Guid.Empty, never an invented one.
Reads a GUID (a 128-bit unique id) in any shape CkUuid accepts — hyphenated, bare hex,
braced, urn:uuid:. As everywhere in CkUuid, there is no substitute fallback for an
identity: missing or unreadable answers Guid.Empty (all zeros), never an invented id. An
all-zeros value in the data also counts as missing by default (treatZerosAsMissing).
var record = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "id": "0F8FAD5B-D9CB-469F-A165-70867728950E" }""");
CkDictionary.ToGuid(dictionary: record, key: "id"); // -> 0f8fad5b-d9cb-469f-a165-70867728950e
CkDictionary.ToGuid(dictionary: record, key: "gone"); // -> Guid.Empty
ToEnum
A word or a number into your enum.
TEnum ToEnum<TEnum>(
object? dictionary = null,
string? key = null,
TEnum fallback = default,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
) where TEnum : struct, Enum
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
fallback |
TEnum |
default |
What comes back when the entry is missing or names no defined member; the enum's zero value when not given. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering the fallback. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The value names a defined member, or is its number | The member: Status.Approved |
The member: Status.Approved |
| Rejected: missing entry, an unknown name, or a number no member defines | The fallback — the enum's zero value when not given |
Throws CkDictionaryException — carries Input, Key and Reason |
Only defined single members come back — no stray casts, no combined [Flags] values.
Reads a member of your own enum type. A name matches in any casing; a number must be the
value of a defined member. Either way the answer is always a defined member — an unknown
name or number answers the fallback, the enum's zero value unless you pick another.
Combined [Flags] values are not read here, only single defined members.
enum Status { Unknown = 0, Draft = 1, Approved = 2 }
var ticket = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "a": "approved", "b": 2, "c": "banana", "d": 99 }""");
CkDictionary.ToEnum<Status>(dictionary: ticket, key: "a"); // -> Status.Approved (any casing)
CkDictionary.ToEnum<Status>(dictionary: ticket, key: "b"); // -> Status.Approved (by value)
CkDictionary.ToEnum<Status>(dictionary: ticket, key: "c"); // -> Status.Unknown (the default fallback)
CkDictionary.ToEnum(
dictionary: ticket,
key: "d",
fallback: Status.Draft
); // -> Status.Draft (99 is not defined)
The next three readers walk nested data. All three answer empty — never null — when
there is nothing usable. And one scalar always wraps to a one-item list: a single piece of
text becomes ["text"], never a list of its characters.
Map
A nested object as a new bag.
Dictionary<string, object?> Map(
object? dictionary = null,
string? key = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering an empty dictionary. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is an object, or any bag shape | A new bag of its entries: { "id": 7, "address": { … } } in the example |
A new bag of its entries: { "id": 7, "address": { … } } in the example |
| Rejected: missing entry, JSON null, or a value that is not dictionary-shaped | Empty dictionary | Throws CkDictionaryException — carries Input, Key and Reason |
Values unwrap one level — deeper objects and arrays wait for the next Map or Maps call.
Map reads a nested object as a new bag you can pass straight back into any method here —
that is how you walk into deeper JSON, one level per call.
var payload2 = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "member": { "id": 7, "address": { "zip": "78701" } } }""");
var member = CkDictionary.Map(dictionary: payload2, key: "member");
CkDictionary.Int32(dictionary: member, key: "id"); // -> 7
var address = CkDictionary.Map(dictionary: member, key: "address");
CkDictionary.String(dictionary: address, key: "zip"); // -> "78701"
Maps
A list of nested objects.
Dictionary<string, object?>[] Maps(
object? dictionary = null,
string? key = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering an empty list. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a list of objects, or one bare object | One bag per object: [{ "id": 1 }, { "id": 2 }] — a single object still lists |
One bag per object: [{ "id": 1 }, { "id": 2 }] — a single object still lists |
| Rejected: missing entry, JSON null, or a value that is neither a list nor an object | Empty list | Throws CkDictionaryException — carries Input, Key and Reason |
Items that are not objects are left out silently; with throwIfInvalid: true they are reported instead.
Maps reads a list of objects ("members": [ { … }, { … } ]) as a list of bags; a single
object where a list was allowed comes back as a one-item list.
var lists = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "members": [ { "id": 1 }, { "id": 2 } ], "single": { "id": 3 }, "mixed": [1, "two", true] }""");
CkDictionary.Maps(dictionary: lists, key: "members"); // -> two bags, ready for a loop
CkDictionary.Maps(dictionary: lists, key: "single"); // -> a one-item list of bags
Items
A list of raw values.
object?[] Items(
object? dictionary = null,
string? key = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering an empty list. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry is a list, or one lone value | The raw values: [1, "two", true] |
The raw values: [1, "two", true] |
| Rejected: missing entry, JSON null, or a value that is itself a dictionary | Empty list | Throws CkDictionaryException — carries Input, Key and Reason |
Whole JSON numbers arrive as long — the 1 in [1, "two", true] is a long.
Items reads a list of raw values the way Value unwraps
them.
// lists is the bag from the Maps example above
CkDictionary.Items(dictionary: lists, key: "mixed"); // -> [1, "two", true] (raw values)
Value
The raw escape hatch.
object? Value(
object? dictionary = null,
string? key = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag to read from — any shape with string keys; see What counts as a bag. |
key |
string? |
null |
The key, matched exactly; tried once more trimmed when the exact key is absent. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says why, instead of answering null, when the entry is missing — a found null never throws. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The entry holds a value | The unwrapped value: 7 (a long — whole JSON numbers arrive as long) |
The unwrapped value: 7 (a long — whole JSON numbers arrive as long) |
The entry exists but holds JSON null or a database DBNull |
null |
null — an honest nothing never throws |
| No dictionary, no key, or no entry under the key | null |
Throws CkDictionaryException — carries Input, Key and Reason |
null honestly means "nothing there" — this is the one method here that answers it.
When no typed method fits, Value hands the entry back unwrapped but not converted, ready
for any other CommonKit reader that takes an object?. A null answer means "nothing
there" — no dictionary, no key, JSON null, or a database DBNull.
var vrow = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "amount": "$1,250.00", "score": 7 }""");
// no CkDictionary.Amount exists — compose with Value:
CkMoney.Amount(CkDictionary.Value(dictionary: vrow, key: "amount")); // -> 1250.00m
CkDictionary.Value(dictionary: vrow, key: "score"); // -> 7 (a long — whole JSON numbers arrive as long)
CkDictionary.Value(dictionary: vrow, key: "gone"); // -> null
At
A value down a dotted path.
object? At(
object? dictionary = null,
string? path = null,
bool throwIfInvalid = CkSettings.DefaultThrowIfInvalid
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag, in any accepted shape. |
path |
string? |
null |
A dotted path, "customer.address.zip", or a plain key. |
throwIfInvalid |
bool |
CkSettings.DefaultThrowIfInvalid (false) |
Pass true to throw a CkDictionaryException that says which segment failed, instead of answering null. |
Returns
| When | Default | With throwIfInvalid: true |
|---|---|---|
| The path leads to a value | The value, unwrapped from its JSON shape | The value |
| It leads nowhere, or to a JSON null | null |
Throws CkDictionaryException — the reason names the segment |
Every reader on this page takes the same paths — Int32(payload, "order.total") works —
so At is the spelling for when you want the raw value, or the intent on the page. The bag
is asked for the whole key first, so a key that holds a dot literally still wins; the path
is walked only when no such entry exists.
CkDictionary.At(payload, "customer.address.zip"); // -> "02134"
CkDictionary.Decimal(payload, "order.total"); // -> 12.50m (every reader takes a path)
CkDictionary.At(payload, "customer.phone"); // -> null
Has
Is the key there at all?
bool Has(
object? dictionary = null,
string? key = null
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag, in any accepted shape. |
key |
string? |
null |
The key, or a dotted path. |
Returns
| When | You get |
|---|---|
| An entry exists under the key, whatever its value — a JSON null included | true |
| No bag, no key, or no such entry | false — it never throws |
The question Value cannot answer, since it says null both for "absent" and for
"present but null".
CkDictionary.Has(payload, "note"); // -> true, even when the note is null
CkDictionary.Has(payload, "phone"); // -> false
MissingKeys
The required fields that are not there.
string[] MissingKeys(
object? dictionary = null,
params IEnumerable<string?> keys
)
| Parameter | Type | Default | What it does |
|---|---|---|---|
dictionary |
object? |
null |
The bag, in any accepted shape. |
keys |
params IEnumerable<string?> |
— | The keys that must be present, plain or dotted. Null and blank entries are skipped. |
Returns
| When | You get |
|---|---|
| Some keys are absent | Those keys, trimmed, in the order asked |
| All are present, or no keys were asked | An empty array — it never throws |
The required-fields check for a form or a payload, in one call, judged as Has judges:
present whatever the value. With no bag, every key is missing.
string[] missing = CkDictionary.MissingKeys(payload, "name", "email", "address.zip");
if (missing.Length > 0) { /* "email, address.zip are required" */ }
Good to know
- Dotted keys are paths.
"customer.address.zip"walks nested maps in every reader, after the bag has been asked for the whole key once — a key that holds a dot literally still wins.Atis the explicit spelling;HasandMissingKeystake paths too. A stray leading or trailing dot is ignored. - Every kind of "nothing" behaves the same. No dictionary, no entry under the key and
a JSON null all take the same road: the method's fallback (or
nullfromValue). - Keys are case-sensitive. Exact match first; only your key's own surrounding spaces
get one trimmed retry.
"name"never finds"Name". - A repeated key: the last value wins, everywhere. A bag built by
MaporCkPayload.FromJson, a keyed read straight off a rawJsonElement, a pair list,FromForm— all answer the last value, which is System.Text.Json's own answer and every common parser's. That matters for a gate: the value the library judges is the value the application's own model binding will use, so a payload cannot show one URL to the check and another to the code. - Whole JSON numbers arrive as
long. That is whatValueandItemshand you for7; the typed readers convert it onward, so you only notice at the escape hatch. - Bags survive their source — with one exception. Bags from
CkPayload.FromJsonare safe forever, but a rawJsonElementfrom aJsonDocumentyou already disposed is not. The library will not crash on it — you just get fallbacks. "published"is not false.Boolanswers the fallback for anything off its token list; check the fallback path, do not assume "unreadable means false".ToEnumnever invents members. Only defined single members come back — no casts of stray numbers, no[Flags]combinations.- Text never splits into characters.
ItemsandStringswrap one scalar into a one-item list; a lone"solo"is["solo"], not["s", "o", "l", "o"].
If you want errors instead
Every method takes throwIfInvalid: true. A missing entry or an unreadable value then
raises a CkDictionaryException (from SafeCoder.CommonKit.Exceptions) instead of answering
the fallback. On top of the usual Input and Reason, this one carries the Key that
failed, so the one-line message tells you exactly which field of the payload to look at.
var strict = JsonSerializer.Deserialize<Dictionary<string, object>>("""{ "id": 7 }""");
CkDictionary.Int32(
dictionary: strict,
key: "gone",
throwIfInvalid: true
); // throws CkDictionaryException: CkDictionary.Int32: cannot read key "gone" (String) — no entry under the key.