StjTokenKit 1.0.0

dotnet add package StjTokenKit --version 1.0.0
                    
NuGet\Install-Package StjTokenKit -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="StjTokenKit" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="StjTokenKit" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="StjTokenKit" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add StjTokenKit --version 1.0.0
                    
#r "nuget: StjTokenKit, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package StjTokenKit@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=StjTokenKit&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=StjTokenKit&version=1.0.0
                    
Install as a Cake Tool

StjTokenKit

Predictable, typed helpers for working with System.Text.Json, Newtonsoft.Json, and CLR object graphs / dynamic values.

Both libraries are capable, but everyday DOM access still gets noisy fast: chained property lookups, awkward nested reads, and unclear behavior around missing values, null, and wrong types. StjTokenKit keeps the raw DOM model but gives it a cleaner, more explicit read surface.

Scope

Version 1 is intentionally small and read-focused across all supported access models:

  • simple path selection
  • typed primitive reads
  • serializer-backed object reads
  • explicit missing/null/wrong-type behavior

Out of scope:

  • mutation helpers
  • full JSONPath support
  • schema validation
  • document transforms

Path syntax

Supported path grammar:

  • dotted property access: user.name
  • array indexes: items[0].id
  • root arrays: [1].id
  • quoted property names: user['full.name'], ["key with spaces"].id
  • empty path: selects the current element

Quoted property names support both single (') and double (") quotes and allow keys that contain dots or other characters that conflict with the path grammar.

Rules:

  • case-sensitive
  • negative indexes are invalid
  • malformed paths throw ArgumentException
  • missing properties and out-of-range indexes are treated as missing

Behavior contract

Every access API treats these states consistently:

  1. Missing: the path does not exist
  2. Null: the path exists and the value is null
  3. Wrong type: the path exists but cannot be read as the requested type
  4. Valid value: the read succeeds

API families:

  • TryGet... returns false for missing, null, and wrong type
  • GetRequired... throws for missing, null, and wrong type
  • Get...OrDefault returns the supplied default for missing and null, and throws for wrong type
  • TrySelect exposes traversal without conversion
  • SelectRequired exposes traversal with path-aware exceptions

Examples

System.Text.Json

using System.Text.Json;
using StjTokenKit;

using var document = JsonDocument.Parse(
    """
    {
      "user": {
        "name": "Ada",
        "age": 42,
        "id": "f9c265be-f4a2-4140-ad95-9ed57cd6ef09"
      },
      "items": [
        { "id": 1 },
        { "id": 2 }
      ]
    }
    """);

var root = document.RootElement;

var name = root.GetRequiredString("user.name");
var age = root.GetInt32OrDefault("user.age", 0);
var ok = root.TryGetGuid("user.id", out var id);
var itemId = document.GetRequiredInt32("items[1].id");

Newtonsoft.Json

using Newtonsoft.Json.Linq;
using StjTokenKit;

var root = JToken.Parse(
    """
    {
      "user": {
        "name": "Ada",
        "age": 42,
        "id": "f9c265be-f4a2-4140-ad95-9ed57cd6ef09"
      },
      "items": [
        { "id": 1 },
        { "id": 2 }
      ]
    }
    """);

var name = root.GetRequiredString("user.name");
var age = root.GetInt32OrDefault("user.age", 0);
var ok = root.TryGetGuid("user.id", out var id);
var itemId = root.GetRequiredInt32("items[1].id");

CLR objects and dynamic

using System.Dynamic;
using StjTokenKit;

dynamic root = new ExpandoObject();
root.User = new ExpandoObject();
root.User.DisplayName = "Ada";
root.Items = new[]
{
    new { Id = 1 },
    new { Id = 2 }
};

var value = DynamicValue.From(root, ignoreCase: true);

var name = value.GetRequiredString("user.displayname");
var itemId = value.GetRequiredInt32("items[1].id");

Selection helpers

if (root.TrySelect("items[0]", out var firstItem))
{
    Console.WriteLine(firstItem.GetRawText());
}

var user = root.SelectRequired("user");
var exists = root.Exists("user.name");
var isNull = root.IsNull("user.middle");

// DOM-specific kind
var kind = root.GetValueKind("user.name"); // JsonValueKind (System.Text.Json only)

// DOM-agnostic kind — works on JsonElement, JToken, and DynamicValueAccessor
var unifiedKind = root.GetKind("user.name"); // StjValueKind

StjValueKind is a unified enum with values Object, Array, String, Number, Boolean, Null, and Missing. It lets you inspect value types without importing DOM-specific enums.

Primitive readers

Available reader families exist for:

  • JsonElement

  • JsonDocument

  • JToken (including JObject and JArray)

  • DynamicValueAccessor over CLR object graphs and dynamic values

  • String

  • Boolean

  • Int32

  • Int64

  • Decimal

  • Double

  • Guid

  • DateTime

  • DateTimeOffset

Example:

var enabled = document.GetBooleanOrDefault("flags.enabled", false);
var created = document.GetRequiredDateTimeOffset("meta.createdAt");

if (!document.TryGetDecimal("pricing.total", out var total))
{
    total = 0m;
}

Nullable primitive readers

When a field is optional but explicitly nullable (present-and-null is valid, missing is an error), use the nullable family:

  • TryGetNullable{Type} — returns false for missing or wrong type; returns true with null for a null value
  • GetRequiredNullable{Type} — throws JsonPathNotFoundException for missing, throws JsonValueTypeException for wrong type, returns null for a null value
// Field is required to exist but may be null
int? count = document.GetRequiredNullableInt32("pagination.count");

// Field is optional; distinguish null from missing
if (document.TryGetNullableInt32("pagination.count", out int? maybeCount))
{
    // path existed (value may be null)
}

Available for: String, Boolean, Int32, Int64, Decimal, Double, Guid, DateTime, DateTimeOffset — across JsonElement, JsonDocument, JToken, and DynamicValueAccessor.

Object readers

Object reads use the native serializer for the underlying DOM:

  • System.Text.Json: JsonSerializer.Deserialize<T>()
  • Newtonsoft.Json: JToken.ToObject<T>(JsonSerializer)
  • CLR object graphs: direct assignment when the selected value already is T, otherwise a System.Text.Json roundtrip
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};

var person = document.GetRequiredObject<Person>("payload.person", options);
var fallback = document.GetObjectOrDefault("payload.optionalPerson", new Person("Unknown", 0), options);

StjTokenKit does not change serializer semantics. If you need custom binding or converters, pass the appropriate serializer/options object for the DOM you are using.

Dynamic accessors

For CLR objects, configure lookup behavior once and then reuse the accessor:

var accessor = someObject.AsDynamicValue(ignoreCase: true);

var exists = accessor.Exists("user.name");
var isNull = accessor.IsNull("user.middle");
var type = accessor.GetValueType("user.name");
var dto = accessor.GetRequiredObject<Person>("user.profile");

Exceptions

Required APIs throw path-aware exceptions:

  • JsonPathNotFoundException
  • JsonValueNullException
  • JsonValueTypeException

Example:

try
{
    var age = document.GetRequiredInt32("user.age");
}
catch (JsonValueTypeException ex)
{
    Console.WriteLine($"{ex.Path}: expected {ex.ExpectedType}, actual {ex.ActualKind}");
}

Notes

  • JsonElement values are still tied to the lifetime of their owning JsonDocument
  • JToken helpers operate on JToken, so the same API works on JObject, JArray, and nested values
  • CLR object helpers operate on public properties, public fields, dictionaries, ExpandoObject, and indexable lists/arrays
  • CLR object lookup is case-sensitive by default and can be made case-insensitive with ignoreCase: true
  • no implicit string-to-number or string-to-bool coercion is performed
  • numeric readers only succeed for numeric token kinds
  • Guid readers only succeed for JSON strings
  • System.Text.Json date readers expect supported string formats
  • Newtonsoft.Json date readers accept both string dates and parsed Date tokens
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 87 4/21/2026