SystemTextJson.FluentApi 1.0.1

dotnet add package SystemTextJson.FluentApi --version 1.0.1                
NuGet\Install-Package SystemTextJson.FluentApi -Version 1.0.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="SystemTextJson.FluentApi" Version="1.0.1" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add SystemTextJson.FluentApi --version 1.0.1                
#r "nuget: SystemTextJson.FluentApi, 1.0.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.
// Install SystemTextJson.FluentApi as a Cake Addin
#addin nuget:?package=SystemTextJson.FluentApi&version=1.0.1

// Install SystemTextJson.FluentApi as a Cake Tool
#tool nuget:?package=SystemTextJson.FluentApi&version=1.0.1                

NuGet WARNING: this package is under active development so the api may change at any time. Normally you do not need to use this package and just copy paste required functionality like NRT or polymorphism support.

SystemTextJson.FluentApi

SystemTextJson.FluentApi is a fluent configuration library for System.Text.Json that allows developers to configure serialization uses strongly typed fluent interface and lambda expression.

Documentation

All api usually repeats attributes from System.Text.Json.Serialization and set corresponding property in JsonPropertyInfo or JsonTypeInfo. Configuration based on IJsonTypeInfoResolver so developers can configure reflection based TypeInfoResolver and source generator JsonSerializerContext.

Quick start

To use FluentApi need to configure JsonSerializerOptions instance via JsonModelBuilder and pass it to serializer.

var options = new JsonSerializerOptions() { WriteIndented = true };
options.ConfigureDefaultTypeResolver(p =>
p.Entity<Person>()
.Property(p => p.LastName).HasName("surname")
.Property(p => p.FirstName).IsIgnored()
.VirtualProperty("FullName", p => $"{p.FirstName} {p.LastName}")
.Property(p => p.Age).HasHumberHandling(JsonNumberHandling.WriteAsString));

var person = new Person() { FirstName = "First name", LastName = "Last name", Age = 12 };
var json = JsonSerializer.Serialize(person, options);

Console.WriteLine(json);

This example produce this JSON

{
  "surname": "Last name",
  "Age": "12",
  "FullName": "First name Last name"
}

Polymorphism serialization

STJ has build in support polymorphic serialization, but user have to annotate base class with JsonDerivedTypeAttribute with all derived types. In fluent API you can configure each derived type manually or find all derived types in runtime.

builder.Entity<Root>()
.HasDerivedType<Derived1>(nameof(Derived1))
.HasDerivedType<Derived2>(nameof(Derived2))
.HasDerivedType<Root>(nameof(Root))
// or
builder.Entity<Root>().HasDerivedTypesFromAssembly(Assembly.GetExecutingAssembly(), t => t.Name)

var testObject = new Root[] 
{
    new Derived1() { Derived1Property = "derived" },
    new Derived2() { Derived2Property = "derived2" },
    new Root(){ RootProperty = "root"}
};


public class Root
{
    public string? RootProperty { get; set; }
}

public class Derived1 : Root
{
    public string? Derived1Property { get; set; }
}

public class Derived2 : Root
{
    public string? Derived2Property { get; set; }
}

Serialization of testObject collection will produce:

[
   {
      "$type":"Derived1",
      "Derived1Property":"derived",
      "RootProperty":null
   },
   {
      "$type":"Derived2",
      "Derived2Property":"derived2",
      "RootProperty":null
   },
   {
      "$type":"Root",
      "RootProperty":"root"
   }
]

With $type discriminator serializer are able to deserialize this collection. Another approach to serialization is use actual type from object instance, instead of property type. To achieve this behavior serializer can threat specific property as object using PropertyBuilder.SerializeAsObject.

builder.Entity<AsObjectTestClass>().Property(p => p.Data).SerializeAsObject();

var testObject = new AsObjectTestClass { Data = new Derived() { Property = "Prop" } };

public class AsObjectTestClass
{
    public Root? Data { get; set; }
}

public class Root { }

public class Derived : Root
{
    public string? Property { get; set; }
}

Serialization of testObject will produce:

{
   "Data":{
      "Property":"Prop"
   }
}

But in this case only serialization is available because JSON does not contain type discriminator and JsonException will be thrown on deserialization.

Nullable reference type support

STJ has build in support of required properties, but it just check, that value exists in JSON on deserialization and does not prevent setting null to none nullable properties. Fluent Api can configure JsonSerializerOptions to respect NRT annotations on fields and properties. Internally it uses JsonPropertyInfo.Set property and reduces deserialization performance.

builder.RespectNullableReferenceType();

JsonSerializer.Deserialize<TestClass>("""{"Property": null}""", _options); // this throws JsonException
JsonAsserts.AssertObject(new TestClass(), "{}", _options); // BUT this is not because Property is not requred.

public class TestClass
{
    public string Property { get; set; }
}

Virtual properties

Fluent Api can define virtual properties, that does not match to any real property in object.

builder.Entity<Person>()
.Property(p => p.LastName).IsIgnored()
.Property(p => p.FirstName).IsIgnored()
.VirtualProperty("FullName", p => $"{p.FirstName} {p.LastName}")

var testObject = new Person() { FirstName = "First name", LastName = "Last name" };

class Person
{
    public string? FirstName { get; set; }

    public string? LastName { get; set; }
}

Serialization of testObject will produce:

{
    "FullName": "First name Last name"
}

Change tracking

Fluent Api can track changes during serialization and deserialization. If some entity implement IHaveChnagedProperties interface with not null ChangedProperties property, it will be used to track changes. To populate property/field names that set deserialization use TrackChangedProperties() method. To serialize properties only from ChangedProperties use SerializeOnlyChangedProperties(). This method will override JsonIgnoreCondition.

builder.TrackChangedProperties().SerializeOnlyChangedProperties();

var testObject = new TrackTestClass()
{
    StringProperty = "str",
    IntProperty = 1,
    ChangedProperties = { nameof(TrackTestClass.IntProperty) }
};

public class TrackTestClass : IHaveChangedProperties
{
    public string? StringProperty { get; set; }
    public int IntProperty { get; set; }
    public ISet<string> ChangedProperties { get; } = new HashSet<string>();
}

Serialization of testObject will produce:

{
    "IntProperty": 1
}

And deserialization of this JSON will populate "IntProperty" value to ChangedProperties.

ValueTuple serialization

Fluent Api has ValueTupleJsonConverter to serialize and deserialize ValueTuple as array.

var options = new JsonSerializerOptions() { Converters = { new ValueTupleJsonConverter() } };
JsonSerializer.Serialize((1,"str"),options);

This code output:

[1,"str"]

Inline arrays support

Fluent Api has InlineArrayJsonConverter for .NET 8 and above to serialize and deserialize InlineArray structs as arrays.

var array = new InlineArray();
array[0] = null;
array[1] = 1;
array[2] = -1;
var options = new JsonSerializerOptions() { Converters = { new InlineArrayJsonConverter() } };
JsonSerializer.Serialize(array,options);

[InlineArray(3)]
private struct InlineArray
{
    public int? Value;
}

Output: "[null,1,-1]"

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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.1 138 1/25/2024
1.0.0 184 11/17/2023
1.0.0-preview.1.0.4 70 11/8/2023
1.0.0-preview.1.0.3 70 11/5/2023
1.0.0-preview.1.0.2 69 11/5/2023
1.0.0-preview.1.0.1 70 11/4/2023