Skip to content

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 the Dictionary<string, object> that JsonSerializer.Deserialize produces;
  • a read-only or old-style non-generic dictionary;
  • a list of key–value pairs;
  • a JsonElement or JsonDocument holding a JSON object, straight from JsonDocument.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. At is the explicit spelling; Has and MissingKeys take 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 null from Value).
  • 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 Map or CkPayload.FromJson, a keyed read straight off a raw JsonElement, 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 what Value and Items hand you for 7; the typed readers convert it onward, so you only notice at the escape hatch.
  • Bags survive their source — with one exception. Bags from CkPayload.FromJson are safe forever, but a raw JsonElement from a JsonDocument you already disposed is not. The library will not crash on it — you just get fallbacks.
  • "published" is not false. Bool answers the fallback for anything off its token list; check the fallback path, do not assume "unreadable means false".
  • ToEnum never invents members. Only defined single members come back — no casts of stray numbers, no [Flags] combinations.
  • Text never splits into characters. Items and Strings wrap 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.