Table of Contents

Getting Started

Installation

Consume Nerdbank.Json from its NuGet package.

Nerdbank.Json NuGet package
Nerdbank.Json.AspNetCoreMvcFormatter NuGet package

Usage

Supported frameworks

The core library targets .NET 10, .NET Framework 4.7.2, and .NET Standard 2.0. Dedicated .NET 8 and .NET 9 targets are no longer provided; applications on those runtimes can use the .NET Standard APIs, but the generic APIs constrained to IShapeable<T> and Native AOT support require .NET 10 or later. The ASP.NET Core MVC integration requires .NET 10 or later.

Building this repository requires the .NET 11 SDK specified in global.json. C# union declarations require C# 15; the examples target .NET 11, while earlier targets need the union compiler types supplied as polyfills.

Generating type shapes

Given a mutable type annotated with GenerateShapeAttribute like this:

[GenerateShape]
public partial class Person
{
    public string? Name { get; set; }

    public int Age { get; set; }
}
Important

All types attributed with GenerateShapeAttribute must be declared with the partial modifier. If these are nested types, all containing types must also have the partial modifier.

You can serialize and deserialize it like this:

JsonSerializer serializer = new();

string json = serializer.Serialize(new Person { Name = "Ada", Age = 37 });
Person person = serializer.Deserialize<Person>(json);

Only the top-level type that you directly serialize needs GenerateShapeAttribute. Referenced property types in the object graph can be discovered through the generated shape provider.

For built-in scalar values, the same serializer APIs work directly:

using Nerdbank.Json;

JsonSerializer serializer = new();

string text = serializer.Serialize(Guid.Parse("01234567-89ab-cdef-0123-456789abcdef"));
Guid value = serializer.Deserialize<Guid>(text);

Supported Values

The current serializer surface includes built-in scalar and selected BCL types, mutable object graphs with settable properties, parameterized-constructor object materialization, mutable ICollection<T> implementations such as List<T>, getter-only mutable collection properties that can be populated in place, and mutable dictionaries whose key type is a supported simple scalar or enum-like type.

Examples include:

  • Primitive numbers and booleans
  • Strings and chars
  • Date and time types
  • Guids, URIs, versions, cultures, encodings
  • Byte buffers
  • Selected drawing primitives such as Color and Point
  • Mutable object graphs annotated with GenerateShapeAttribute
  • Mutable lists and supported-key dictionaries
  • Closed unions annotated with DerivedTypeShapeAttribute
  • C# unions, including recursive unions and types implementing the C# union member pattern

By default, object property names serialize as camelCase. Dictionary keys remain unchanged unless DictionaryKeyNamingPolicy is explicitly set.

When deserializing through a parameterized constructor, required constructor parameters must be present in JSON and duplicate assignments to the same constructor parameter are rejected.

When deserializing properties, explicit JSON null is rejected for non-nullable reference-type properties. Getter-only mutable collection properties are populated into their existing instances rather than replaced.

String Escaping

Strings are escaped according to RFC 8259 requirements. Characters that do not require escaping are preserved as-is instead of being over-escaped.

Current Limitations

Delegate serialization and arbitrary complex-object dictionary keys are not supported. Read-only scalar properties require constructor binding for deserialization. Read-only and immutable collections are supported when their PolyType shapes supply a construction strategy; parameterized collection construction buffers elements before constructing the collection.