System.Text.Json ServiceStack APIs

System.Text.Json ServiceStack APIs Background
5 min read

In continuing our focus to enable ServiceStack to become a deeply integrated part of .NET 8 Application's, ServiceStack latest .NET 8 templates now default to using standardized ASP.NET Core features wherever possible, including:

This reduces friction for integrating ServiceStack into existing .NET 8 Apps, encourages greater knowledge and reuse and simplifies .NET development as developers have a reduced number of concepts to learn, fewer technology implementations to configure and maintain that are now applied across their entire .NET App.

The last integration piece supported was utilizing System.Text.Json - the default high-performance async JSON serializer used in .NET Applications, can now be used by ServiceStack APIs to serialize and deserialize its JSON API Responses that's enabled by default when using Endpoint Routing.

This integrates ServiceStack APIs more than ever where just like Minimal APIs and Web API, uses ASP.NET Core's IOC to resolve dependencies, uses Endpoint Routing to Execute APIs that's secured with ASP.NET Core Identity Auth then uses System.Text.Json to deserialize and serialize its JSON payloads.

Enabled by Default when using Endpoint Routing

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

Enhanced Configuration

ServiceStack uses a custom JsonSerializerOptions to improve compatibility with existing ServiceStack DTOs and ServiceStack's rich ecosystem of generic Add ServiceStack Reference Service Clients, which is configured to:

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

Benefits all Add ServiceStack Reference Languages

This compatibility immediately benefits all of ServiceStack's Add ServiceStack Reference native typed integrations for 11 programming languages which all utilize ServiceStack's JSON API endpoints - now serialized with System.Text.Json

Support for DataContract Annotations

Support for .NET's DataContract serialization attributes was added using a custom TypeInfoResolver, specifically it 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, especially when you need to support external clients that can't be updated.

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

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

Or limit to only use System.Text.Json for 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 its 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.