LinKit.Json.Runtime 1.2.1

dotnet add package LinKit.Json.Runtime --version 1.2.1
                    
NuGet\Install-Package LinKit.Json.Runtime -Version 1.2.1
                    
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="LinKit.Json.Runtime" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LinKit.Json.Runtime" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="LinKit.Json.Runtime" />
                    
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 LinKit.Json.Runtime --version 1.2.1
                    
#r "nuget: LinKit.Json.Runtime, 1.2.1"
                    
#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 LinKit.Json.Runtime@1.2.1
                    
#: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=LinKit.Json.Runtime&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=LinKit.Json.Runtime&version=1.2.1
                    
Install as a Cake Tool

LinKit.Json.Runtime

LinKit.Json.Runtime is a .NET library for JSON serialization, deserialization, and single-value path queries. It provides a convenient System.Text.Json-based API, interoperability with Newtonsoft.Json JToken, optional SpanJson helpers, and metadata-based APIs for applications using source generation or Native AOT.

The library targets .NET 8 and .NET 9.

Contents

Installation

Install the NuGet package:

dotnet add package LinKit.Json.Runtime

Or build the library from the repository:

dotnet build LinKit.Json.Runtime\LinKit.Json.Runtime.csproj

The package references Newtonsoft.Json and SpanJson for its integration APIs. System.Text.Json is provided by the target .NET runtime.

Choosing an API

Scenario Recommended API
General-purpose serialization in a regular JIT application ToJson, FromJson<T>, FromBytes<T>
Native AOT or trimming with generated/static System.Text.Json metadata *WithTypeInfo APIs
UTF-8 serialization/deserialization with System.Text.Json ToUtf8JsonBytesWithTypeInfo, FromBytesWithTypeInfo or FromBytes<T>
Query an in-memory System.Text.Json document JPath extensions for JsonNode, JsonElement, or JsonDocument
Query JSON text without retaining a parsed document JPath extensions for string
Query a Newtonsoft JSON tree JPath extensions for JToken, or Newtonsoft's SelectTokens for JSONPath operators
Use SpanJson directly ToJsonUtf16, ToJsonUtf8Bytes, FromJsonUtf16<T>, FromJsonUtf8<T>

The *WithTypeInfo methods use exactly the JsonTypeInfo<T> supplied by the caller. They do not apply JConvertOptions or initialize JConvert's reflection-based compatibility pipeline.

JConvert

Import the namespace:

using LinKit.Json.Runtime;

Serialize and deserialize

var customer = new Customer(42, "Ada Lovelace");

string json = customer.ToJson();
Customer? copy = json.FromJson<Customer>();

public sealed record Customer(int Id, string Name);

ToJson serializes using the object's runtime type. FromJson<T> and FromBytes<T> deserialize to the requested type; FromBytes<T> accepts UTF-8 JSON bytes. Empty or whitespace-only JSON strings, and empty byte arrays, return default; malformed non-empty JSON can throw JsonException.

Configure serialization

ToJson accepts an optional action that configures a JConvertOptions instance for that call:

string json = customer.ToJson(options =>
{
    options.NamingConvention = NamingConvention.CamelCase;
    options.WriteIndented = true;
    options.EnumSerialization = EnumSerializationMode.AsString;
    options.LoopHandling = ReferenceLoopHandling.Ignore;
});

Available naming conventions are:

Value Result example
NamingConvention.Unchanged CustomerName
NamingConvention.PascalCase CustomerName
NamingConvention.CamelCase customerName
NamingConvention.SnakeCaseLower customer_name
NamingConvention.KebabCaseLower customer-name

Unchanged is the default. PascalCase uppercases the first character; it does not apply a general word-boundary transformation.

Enum serialization defaults to EnumSerializationMode.AsNumber. Set it to AsString to serialize enum values as their names. The flexible enum converter used by the general-purpose APIs accepts enum names and integer values when deserializing.

Reference loop handling is configured with ReferenceLoopHandling:

  • Throw (default): do not configure a reference handler; cyclic graphs fail during serialization.
  • Ignore: use System.Text.Json's ignore-cycles behavior.
  • Preserve: use System.Text.Json reference metadata such as $id and $ref.

Preserve represents reference identity in JSON. It does not expand a cyclic graph indefinitely.

Configure System.Text.Json options

FromJson<T> and FromBytes<T> accept a callback that configures JsonSerializerOptions:

Customer? customer = json.FromJson<Customer>(options =>
{
    options.PropertyNameCaseInsensitive = true;
    options.NumberHandling = System.Text.Json.Serialization.JsonNumberHandling
        .AllowReadingFromString;
});

For ToJson, advanced System.Text.Json settings and converters are available through JConvertOptions.SystemTextJsonOptions:

string json = customer.ToJson(options =>
{
    options.SystemTextJsonOptions.PropertyNamingPolicy =
        System.Text.Json.JsonNamingPolicy.CamelCase;
    options.SystemTextJsonOptions.Converters.Add(new CustomerIdConverter());
});

The configured custom converters are applied before the library's default converters. Use the high-level properties on JConvertOptions for naming convention, indentation, enum serialization, and reference loop behavior.

When serializing a Newtonsoft JToken, JConvert uses Newtonsoft.Json for that operation. SystemTextJsonOptions and the optional JsonSerializerContext are not used by that special path.

Untyped object values

The general-purpose deserialization APIs register ObjectToInferredTypesConverter. Values declared as object are materialized recursively:

  • JSON objects become Dictionary<string, object?>.
  • JSON arrays become List<object?>.
  • Strings, booleans, and null retain their corresponding CLR representations.
  • Integral JSON numbers that fit in Int64 become long; other numbers are read as double.

For example:

var payload = """{"name":"Ada","scores":[10,20],"active":true}"""
    .FromJson<Dictionary<string, object?>>();

var name = (string?)payload!["name"];
var scores = (List<object?>)payload["scores"]!;

The inferred representation is convenient for dynamic data, but it is not a substitute for a DTO when the JSON schema is known.

Source-generated metadata and Native AOT

For trimming and Native AOT, generate System.Text.Json metadata at build time and pass the matching JsonTypeInfo<T> to a *WithTypeInfo method:

using System.Text.Json.Serialization;
using LinKit.Json.Runtime;

var person = new Person("Ada", 36);
var personTypeInfo = AppJsonContext.Default.Person;

string json = person.ToJsonWithTypeInfo(personTypeInfo);
Person? copy = json.FromJsonWithTypeInfo(personTypeInfo);

byte[] utf8 = person.ToUtf8JsonBytesWithTypeInfo(personTypeInfo);
Person? fromBytes = utf8.FromBytesWithTypeInfo(personTypeInfo);

public sealed record Person(string Name, int Age);

[JsonSerializable(typeof(Person))]
internal partial class AppJsonContext : JsonSerializerContext
{
}

The same metadata can be used when converting a value found by JPath:

using System.Text.Json;
using LinKit.Json.Runtime;

using var document = JsonDocument.Parse(
    """{"profile":{"name":"Ada","age":36}}"""
);

if (document.TryGetValueByPathWithTypeInfo(
        "profile",
        out Person? profile,
        AppJsonContext.Default.Person))
{
    Console.WriteLine(profile!.Name);
}

Use [JsonSourceGenerationOptions] on the context to set metadata-generation defaults such as property naming and case sensitivity. The metadata supplied by the caller controls the serialization contract. *WithTypeInfo methods do not merge in JConvertOptions, JConvert's converters, or a reflection fallback.

The regular ToJson, FromJson<T>, FromBytes<T>, and SerializeCanonical APIs remain compatibility APIs and may require reflection or dynamic code. They are annotated for trimming/AOT analysis. Passing a JsonSerializerContext to the general-purpose ToJson or FromJson<T> overload does not make those overloads the recommended Native AOT path.

The Newtonsoft.Json and SpanJson integrations are separate compatibility paths. Do not assume those integrations are Native AOT-safe merely because the System.Text.Json *WithTypeInfo path is.

JPath

JPath selects a single JSON value using property and array-index segments. It supports:

  • Dot-separated properties: profile.name
  • Array indexes: items[0]
  • Negative indexes from the end: items[-1]
  • A root prefix: $.profile.name
  • Quoted property names containing dots, spaces, quotes, or other special characters: $['settings.theme'], $["user's name"]
  • Escaped characters in quoted property names, including backslash, quote, and \uXXXX escapes

It does not implement wildcard selection, filters, recursive descent, or multi-result queries. Use Newtonsoft.Json's SelectTokens when those JSONPath features are required.

Select a node or element

using System.Text.Json.Nodes;
using LinKit.Json.Runtime;

JsonNode? root = JsonNode.Parse(
    """{"users":[{"name":"Ada"},{"name":"Grace"}]}"""
);

JsonNode? firstUser = root.SelectNode("users[0]");
JsonNode? lastName = root.SelectNode("$.users[-1].name");

SelectNode returns null both when the path is missing and when the selected JSON value is null. Use TrySelectNode to distinguish these cases:

if (root.TrySelectNode("optional", out JsonNode? optional))
{
    // The path exists. optional may be null when the JSON value is null.
}
else
{
    // The path does not exist.
}

For JsonElement, SelectElement returns null when the path is missing. A selected JSON null is represented by a JsonElement whose ValueKind is JsonValueKind.Null.

using var document = JsonDocument.Parse(
    """{"settings.theme":"dark","values":[10,20,30]}"""
);

JsonElement? theme = document.RootElement.SelectElement("$['settings.theme']");
JsonElement? lastValue = document.SelectElement("values[-1]");

The JsonElement returned from JsonDocument or its root element is backed by that document. Keep the document alive while using the element. SelectElement called on a JSON string parses and clones the selected element, so that result is independent of the temporary document.

Convert a selected value

TryGetValueByPath<T> returns false when the path is missing or the value cannot be converted. GetValueByPath<T> returns the value or throws JsonException if selection or conversion fails.

if (root.TryGetValueByPath<string>("users[0].name", out var name))
{
    Console.WriteLine(name);
}

string firstName = root.GetValueByPath<string>("users[0].name");

TryGetValueByPath<T> treats JSON null as a successful result when T can accept null (for example, a reference type or nullable value type). It returns false for JSON null when T is a non-nullable value type.

For parsed JSON scalar values, JPath supports direct conversion for common primitive types and exact numeric conversions. Integral numeric conversions require an integral value and must fit the target type. Complex values are deserialized using System.Text.Json and the optional JsonSerializerOptions.

Query JSON text directly

For a single lookup, query JSON text directly:

string json = """{"orders":[{"id":101},{"id":102}]}""";

if (json.TryGetValueByPath<int>("orders[-1].id", out int orderId))
{
    Console.WriteLine(orderId); // 102
}

For multiple queries, parse once and reuse a JsonDocument to avoid parsing the same text repeatedly:

using var document = JsonDocument.Parse(json);

int firstId = document.GetValueByPath<int>("orders[0].id");
int lastId = document.GetValueByPath<int>("orders[-1].id");

String-based TryGetValueByPath returns false for empty or whitespace-only JSON input. Invalid, non-empty JSON is not silently accepted and can throw JsonException.

Newtonsoft.Json integration

JConvert recognizes a Newtonsoft JToken passed to ToJson and serializes it using Newtonsoft.Json. For path queries, use the token-specific selection methods:

using LinKit.Json.Runtime;
using Newtonsoft.Json.Linq;

JToken token = JToken.Parse(
    """{"settings.theme":"dark","items":[{"id":1},{"id":2}]}"""
);

JToken? theme = token.SelectJToken("$['settings.theme']");

if (token.TryGetValueByPath<int>("items[-1].id", out int id))
{
    Console.WriteLine(id); // 2
}

TrySelectJToken provides an explicit found/not-found result. The typed TryGetValueByPath<T> overload without JsonTypeInfo<T> uses Newtonsoft.Json's ToObject<T>() conversion behavior and may use runtime metadata.

For source-generated metadata, use the JToken overload that accepts JsonTypeInfo<T>:

bool found = token.TryGetValueByPathWithTypeInfo(
    "items[0]",
    out Item? item,
    AppJsonContext.Default.Item);

This metadata overload serializes the selected token to JSON text and deserializes it with the provided System.Text.Json metadata. It does not use Newtonsoft's ToObject<T>() conversion semantics.

JPath's shared property/index syntax is intentionally a subset of JSONPath. For Newtonsoft-specific query operators or multiple results, use JToken.SelectTokens(path) directly.

SpanJson integration

The SpanJson helpers use the library's default SpanJsonResolver<TSymbol>:

using LinKit.Json.Runtime;

var message = new Message("ready", 3);

string jsonUtf16 = message.ToJsonUtf16();
byte[] spanJsonUtf8 = message.ToJsonUtf8Bytes();

Message fromUtf16 = jsonUtf16.FromJsonUtf16<Message>();
Message fromUtf8 = spanJsonUtf8.FromJsonUtf8<Message>();

public sealed record Message(string State, int Attempt);

These methods use SpanJson's resolver and options, not System.Text.Json's JsonTypeInfo<T> or JConvertOptions. Validate SpanJson support separately for trimming or Native AOT if your application requires it.

Canonical JSON

JConvert provides canonical serialization entry points:

string canonical = value.SerializeCanonical();
byte[] canonicalUtf8 = value.SerializeCanonicalToBytes();

An IJsonTypeInfoResolver can be supplied:

string canonical = value.SerializeCanonical(AppJsonContext.Default);

Canonical output recursively sorts object properties by ordinal UTF-16 order, removes insignificant whitespace, uses ECMAScript-style IEEE 754 number formatting, and rejects duplicate object property names and non-finite numbers. Decimal and integer JSON values are interpreted as IEEE 754 doubles during canonicalization; values outside that numeric model may lose precision, so do not use this API when exact arbitrary-precision numbers must be preserved. The parameterless resolver path uses DefaultJsonTypeInfoResolver and is reflection-based. Validate custom converters and resolver behavior against the RFC 8785 test vectors before relying on the output for signatures or hashes.

Behavior and limitations

  • The library targets net8.0 and net9.0.
  • General-purpose JConvert APIs use System.Text.Json for .NET types and Newtonsoft.Json for the JToken serialization special case.
  • JConvert's general-purpose deserialization APIs install library converters for enum handling, object inference, JsonNode, JToken, and SpanJson dynamic objects.
  • JsonTypeInfo<T> APIs use caller-provided metadata directly. Configure naming, converters, and other serializer behavior on that metadata or its source-generation context.
  • JPath supports one property/index path and one selected value. It is not a full JSONPath query engine.
  • JPath's numeric conversion behavior is designed to avoid silent fractional or out-of-range integer conversions.
  • Serialization-option caching reduces repeated configuration work; custom per-call options may require a copy and additional metadata setup. This documentation makes no benchmark-based throughput guarantees.
  • JConvertOptions.SystemTextJsonOptions is used by the general-purpose ToJson API. Deserialization options are configured through the callback accepted by FromJson<T> or FromBytes<T>.

Build and smoke tests

Build the library:

dotnet build LinKit.Json.Runtime\LinKit.Json.Runtime.csproj -c Release

Run the compatibility smoke application:

dotnet run --project CompatibilitySmokeTest\CompatibilitySmokeTest.csproj -c Release

Run the Native AOT smoke application on Windows x64:

dotnet publish AotSmokeTest\AotSmokeTest.csproj -c Release -f net8.0 -r win-x64
dotnet publish AotSmokeTest\AotSmokeTest.csproj -c Release -f net9.0 -r win-x64

The smoke applications cover representative API behavior; they are not a substitute for application-specific tests or performance benchmarks.

License

The project declares the MIT license in its package metadata. See the repository for the license text and contribution information.

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 is compatible.  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 (3)

Showing the top 3 NuGet packages that depend on LinKit.Json.Runtime:

Package Downloads
O24Platform

Nền tảng thư viện dùng chung cho các dự án O24.

O24OpenAPI.Framework

Package Description

O24OpenAPI.Core

Package Description

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.1 93 10/24/2026
1.2.0 1,354 7/9/2026
1.0.1 1,335 3/15/2026
1.0.0 547 12/18/2025

LinKit.Json.Runtime 1.2.1
     - Improved JPath selection and typed conversion across supported JSON sources.
     - Added source-generated metadata APIs for trimming and Native AOT scenarios.
     - Improved canonical JSON output and custom System.Text.Json option handling.
     - Targets .NET 8 and .NET 9.