System.Text.Json APIs

From ServiceStack v8.1, ASP.NET Core .NET 8 Project Templates are configured to use Endpoint Routing that by default utilizes the default high-performance System.Text.Json JSON serializer to serialize and deserialize its JSON API Responses:

Enabled by Default when using Endpoint Routing

app.UseServiceStack(new AppHost(), options => {
    options.MapEndpoints();
});

Enhanced System.Text.Json

To improve compatibility with existing ServiceStack DTOs using ServiceStack.Text JSON Serializer and ServiceStack's rich ecosystem of generic Add ServiceStack Reference Service Clients, ServiceStack uses a customized JsonSerializerOptions which is configured to:

  • Uses CamelCaseNamingPolicy for property names
  • Supports Case Insensitive Properties
  • Not serialize null properties
  • Serializes TimeSpan and TimeOnly Data Types with XML Schema Time format
  • Supports [DataContract] annotations
  • Supports Custom Enum Serialization

Support for DataContract Annotations

Support for .NET's DataContract serialization attributes was added using a custom TypeInfoResolver, that supports:

  • [DataContract] - When annotated, only [DataMember] properties are serialized
  • [DataMember] - Specify a custom Name or Order of properties
  • [IgnoreDataMember] - Ignore properties from serialization
  • [EnumMember] - Specify a custom value for Enum values

Custom Enum Serialization

Below is a good demonstration of the custom Enum serialization support which matches ServiceStack.Text's behavior:

public enum EnumType { Value1, Value2, Value3 }

[Flags]
public enum EnumTypeFlags { Value1, Value2, Value3 }

public enum EnumStyleMembers
{
    [EnumMember(Value = "lower")]
    Lower,
    [EnumMember(Value = "UPPER")]
    Upper,
}

return new EnumExamples {
    EnumProp = EnumType.Value2, // String value by default
    EnumFlags = EnumTypeFlags.Value2 | EnumTypeFlags.Value3, // [Flags] as int
    EnumStyleMembers = EnumStyleMembers.Upper, // Serializes [EnumMember] value
    NullableEnumProp = null, // Ignores nullable enums
};

Which serializes to:

{
  "enumProp": "Value2",
  "enumFlags": 3,
  "enumStyleMembers": "UPPER"
}

Custom Configuration

You can further customize the JsonSerializerOptions used by ServiceStack by using ConfigureJsonOptions() to add any customizations that you can optionally apply to ASP.NET Core's JSON APIs and MVC with:

builder.Services.ConfigureJsonOptions(options => {
    options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
})
.ApplyToApiJsonOptions()  // Apply to ASP.NET Core's JSON APIs
.ApplyToMvcJsonOptions(); // Apply to MVC

Control over when and where System.Text.Json is used

Whilst System.Text.Json is highly efficient, it's also very strict in the inputs it accepts where you may want to revert back to using ServiceStack's JSON Serializer for specific APIs, esp. when needing to support non-updatable external clients.

This can be done by annotating Request DTOs with [SystemJson] attribute, specifying when to use System.Text.Json, e.g: you can limit to only use it for serializing an APIs Response with:

[SystemJson(UseSystemJson.Response)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

Or limit to only use System.Text.Json for deserializing an APIs Request with:

[SystemJson(UseSystemJson.Request)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

Or not use System.Text.Json at all for an API with:

[SystemJson(UseSystemJson.Never)]
public class CreateUser : IReturn<IdResponse>
{
    //...
}

JsonApiClient Support

When Endpoints Routing is configured, the JsonApiClient will also be configured to utilize the same System.Text.Json options to send and receive its JSON API Requests which also respects the [SystemJson] specified behavior.

Clients external to the .NET App can be configured to use System.Text.Json with:

ClientConfig.UseSystemJson = UseSystemJson.Always;

Whilst any custom configuration can be applied to its JsonSerializerOptions with:

TextConfig.ConfigureSystemJsonOptions(options => {
    options.PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower;
});

Scoped JSON Configuration

We've also added partial support for Customized JSON Responses for the following customization options:

Name Alias
EmitCamelCaseNames eccn
EmitLowercaseUnderscoreNames elun
EmitPascalCaseNames epcn
ExcludeDefaultValues edv
IncludeNullValues inv
Indent pp

These can be applied to the JSON Response by returning a decorated HttpResult with a custom ResultScope, e.g:

return new HttpResult(responseDto) {
    ResultScope = () => 
        JsConfig.With(new() { IncludeNullValues = true, ExcludeDefaultValues = true })
};

They can also be requested by API consumers by adding a ?jsconfig query string with the desired option or alias, e.g:

/api/MyRequest?jsconfig=EmitLowercaseUnderscoreNames,ExcludeDefaultValues
/api/MyRequest?jsconfig=eccn,edv

SystemJsonCompatible

Another configuration automatically applied when System.Text.Json is enabled is:

JsConfig.SystemJsonCompatible = true;

Which is being used to make ServiceStack's JSON Serializer more compatible with System.Text.Json output so it's easier to switch between the two with minimal effort and incompatibility. Currently this is only used to override DateTime and DateTimeOffset behavior which uses System.Text.Json for its Serialization/Deserialization.