SpanJson 4.2.1

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

// Install SpanJson as a Cake Tool
#tool nuget:?package=SpanJson&version=4.2.1                

SpanJson

NuGet

See https://github.com/Tornhoof/SpanJson/wiki/Performance for Benchmarks

What is supported

  • Serialization and Deserialization into/from byte arrays, strings, TextWriter/TextReader and streams
  • Serialization and Deserialization of Arrays, Lists, Complex types of the following Base Class Library types:
    • bool, char
    • sbyte, Int16, Int32, Int64
    • byte, UInt16, UInt32, UInt64
    • Single, Double, decimal
    • DateTime, DateTimeOffset, TimeSpan, DateOnly, TimeOnly
    • string, Uri
    • Guid, Version
    • Tuple<,>,ValueTuple<,>, KeyValuePair<,>
  • Public Properties and Fields are considered for serialization/deserialization
  • DateTime{Offset} is in ISO8601 mode with profile https://www.w3.org/TR/NOTE-datetime
  • Dynamics
  • Enums (string and integer, for integer see section Custom Resolver), incl. Flags
  • Anonymous types
  • Dictionary, ConcurrentDictionary with string/int/enum as key, the enum is formatted as a string
  • Serialization/Deserialization of most IEnumerable<T> types (Stack and ConcurrentStack are not supported)
  • Support for [DataMember(Name="MemberName")] to set field name
  • Support for [IgnoreDataMember] to ignore a specific member
  • Support for ShouldSerializeXXX() method pattern to decide at runtime if a member should be serialized
  • Support for [EnumMember] to specify the string value of the enum value
  • Support for Immutable Collections, full Serialization/Deserialization for ImmutableList, ImmutableArray, ImmutableDictionary, ImmutableSortedDictionary. ImmutableStack is not supported
  • Support for read-only collections, ReadOnlyCollection, ReadOnlyDictionary, they are deserialized into a writeable type (i.e. List or Dictionary), then the read-only version is created via an appropriate constructor overload
  • Support for tuples currently excludes the last type with 8 arguments (TRest)
  • Support for annotating a constructor with [JsonConstructor] to use that one instead of assigning members during deserialization
  • Support for custom serializers with [JsonCustomSerializer] to use that one instead of the normal formatter, see examples below
  • Support for Base64 encoded byte arrays, see the Custom Resolvers example below
  • Support for annotating a IDictionary<string,object> with [JsonExtensionData]. Serialization will write all values from the dictionary as additional attributes. Deserialization will deserialize all unknown attributes into it. This does not work together with the Dynamic Language Runtime (DLR) support or the [JsonConstructor] attribute. See example below. The Dictionary will also honor the Case Setting (i.e. CamelCase) and null behaviour for the dictionary keys
  • Pretty printing JSON
  • Minify JSON
  • Different 'Resolvers' to control general behaviour:
    • Exclude Nulls with Camel Case: ExcludeNullsCamelCaseResolver
    • Exclude Nulls with Original Case (default): ExcludeNullsOriginalCaseResolver
    • Include Nulls with Camel Case: IncludeNullsCamelCaseResolver
    • Include Nulls with Original Case: IncludeNullsOriginalCaseResolver
  • Custom Resolvers to control behaviour how you desire

How to use it

// Synchronous API:
var result = JsonSerializer.Generic.Utf16.Serialize(input);
var result = JsonSerializer.NonGeneric.Utf16.Serialize(input);
var result = JsonSerializer.Generic.Utf16.Deserialize<Input>(input);
var result = JsonSerializer.NonGeneric.Utf16.Deserialize(input, typeof(Input));

var result = JsonSerializer.Generic.Utf8.Serialize(input);
var result = JsonSerializer.NonGeneric.Utf8.Serialize(input);
var result = JsonSerializer.Generic.Utf8.Deserialize<Input>(input);
var result = JsonSerializer.NonGeneric.Utf8.Deserialize(input, typeof(Input));

// The following methods return an ArraySegment from the ArrayPool, you NEED to return it yourself after working with it.
var result = JsonSerializer.Generic.Utf16.SerializeToArrayPool(input);
var result = JsonSerializer.NonGeneric.Utf16.SerializeToArrayPool(input);
var result = JsonSerializer.Generic.Utf8.SerializeToArrayPool(input);
var result = JsonSerializer.NonGeneric.Utf8.SerializeToArrayPool(input);

// Asynchronous API:
ValueTask result = JsonSerializer.Generic.Utf16.SerializeAsync(input, textWriter, cancellationToken);
ValueTask result = JsonSerializer.NonGeneric.Utf16.SerializeAsync(input, textWriter, cancellationToken);
ValueTask<Input> result = JsonSerializer.Generic.Utf16.DeserializeAsync<Input>(textReader,cancellationToken);
ValueTask<object> result = JsonSerializer.NonGeneric.Utf16.DeserializeAsync(textReader,typeof(Input),cancellationToken);
ValueTask result = JsonSerializer.Generic.Utf8.SerializeAsync(input, stream, cancellationToken);
ValueTask result = JsonSerializer.NonGeneric.Utf8.SerializeAsync(input, stream, cancellationToken);
ValueTask<Input> result = JsonSerializer.Generic.Utf8.DeserializeAsync<Input>(input, stream, cancellationToken);
ValueTask<object> result = JsonSerializer.NonGeneric.Utf8.DeserializeAsync(input, stream, typeof(Input) cancellationToken);

// To use other resolvers use the appropriate overloads,e.g.:
var serialized = JsonSerializer.NonGeneric.Utf16.Serialize<Input, IncludeNullsOriginalCaseResolver<char>>(includeNull);

// Pretty printing:
var pretty = JsonSerializer.PrettyPrinter.Print(serialized); // this works by reading the JSON and writing it out again with spaces and line breaks

// Minify:
var minified = JsonSerializer.Minifier.Minify(serialized); // this works by reading the JSON and writing it out again without spaces and line breaks

Full example:

using System;
using SpanJson;

namespace Test
{
    public class Program
    {
        private static void Main(string[] args)
        {
            var input = new Input { Text = "Hello World" };
            var serialized = JsonSerializer.Generic.Utf16.Serialize(input);
            var deserialized = JsonSerializer.Generic.Utf16.Deserialize<Input>(serialized);
        }
    }

    public class Input
    {
        public string Text { get; set; }
    }
}
using System;
using SpanJson;

namespace Test
{
    // This JsonConstructorAttribute assumes that the constructor parameter names are the same as the member names (case insensitive comparison, order is not important)
    public class DefaultDO
    {
        [JsonConstructor]
        public DefaultDO(string key, int value)
        {
            Key = key;
            Value = value;
        }

        public string Key { get; }
        public int Value { get; }
    }

    // This JsonConstructorAttribute allows overwriting the matching names of the constructor parameter names to allow for different member names vs. constructor parameter names, order is important here
    public readonly struct NamedDO
    {
        [JsonConstructor(nameof(Key), nameof(Value))]
        public NamedDO(string first, int second)
        {
            Key = first;
            Value = second;
        }

        public string Key { get; }
        public int Value { get; }
    }
}
// Type with a custom serializer to (de)serialize the long value into/from string
public class TestDTO
{
    [JsonCustomSerializer(typeof(LongAsStringFormatter), "Hello World")]
    public long Value { get; set; }
}

// Serializes the Long into a string
public sealed class LongAsStringFormatter : ICustomJsonFormatter<long>
{
    public static readonly LongAsStringFormatter Default = new LongAsStringFormatter();

    public object Arguments { get; set; } // the Argument from the attribute will be assigned

    public void Serialize(ref JsonWriter<char> writer, long value)
    {
        StringUtf16Formatter.Default.Serialize(ref writer, value.ToString(CultureInfo.InvariantCulture));
    }

    public long Deserialize(ref JsonReader<char> reader)
    {
        var value = StringUtf16Formatter.Default.Deserialize(ref reader);
        if (long.TryParse(value, out long longValue))
        {
            return longValue;
        }

        throw new InvalidOperationException("Invalid value.");
    }

    public void Serialize(ref JsonWriter<byte> writer, long value)
    {
        StringUtf8Formatter.Default.Serialize(ref writer, value.ToString(CultureInfo.InvariantCulture));
    }

    public long Deserialize(ref JsonReader<byte> reader)
    {
        var value = StringUtf8Formatter.Default.Deserialize(ref reader);
        if (long.TryParse(value, out long longValue))
        {
            return longValue;
        }

        throw new InvalidOperationException("Invalid value.");
    }
}
// It's possible to annotate custom types a custom formatter to always use the custom formatter
[JsonCustomSerializer(typeof(TwcsCustomSerializer))]
public class TypeWithCustomSerializer : IEquatable<TypeWithCustomSerializer>
{
    public long Value { get; set; }
}

// Instead of copying the implementation of for serialize/deserialize for utf8/utf16
// it is possible to use the writer/reader methods which support both, there is no or only a very minor performance difference
public sealed class TwcsCustomSerializer : ICustomJsonFormatter<TypeWithCustomSerializer>
{
    public static readonly TwcsCustomSerializer Default = new TwcsCustomSerializer();

    public object Arguments { get; set; }

    private void SerializeInternal<TSymbol>(ref JsonWriter<TSymbol> writer, TypeWithCustomSerializer value) where TSymbol : struct
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        writer.WriteBeginObject();
        writer.WriteName(nameof(TypeWithCustomSerializer.Value));
        writer.WriteInt64(value.Value);
        writer.WriteEndObject();
    }

    public void Serialize(ref JsonWriter<byte> writer, TypeWithCustomSerializer value)
    {
        SerializeInternal(ref writer, value);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private TypeWithCustomSerializer DeserializeInternal<TSymbol>(ref JsonReader<TSymbol> reader) where TSymbol : struct
    {
        if (reader.ReadIsNull())
        {
            return null;
        }

        reader.ReadBeginObjectOrThrow();
        var result = new TypeWithCustomSerializer {Value = reader.ReadInt64()};
        reader.ReadEndObjectOrThrow();
        return result;
    }

    public TypeWithCustomSerializer Deserialize(ref JsonReader<byte> reader)
    {
        return DeserializeInternal(ref reader);
    }

    public void Serialize(ref JsonWriter<char> writer, TypeWithCustomSerializer value)
    {
        SerializeInternal(ref writer, value);
    }

    public TypeWithCustomSerializer Deserialize(ref JsonReader<char> reader)
    {
        return DeserializeInternal(ref reader);
    }
}
// Below class will serialize Key and Value and any additional key-value-pair from the dictionary
public class ExtensionTest
{
    public string Key;
    public string Value;

    [JsonExtensionData]
    public IDictionary<string, object> AdditionalValues { get; set; }
}

ASP.NET Core 6.0+ Formatter

You can enable SpanJson as the default JSON formatter in ASP.NET Core 6.0+ by using the NuGet package SpanJson.AspNetCore.Formatter. To enable it, add one of the following extension methods to the AddMvc() call in ConfigureServices

  • AddSpanJson for a resolver with ASP.NET Core 6.0 defaults: IncludeNull, CamelCase, Integer Enums
  • AddSpanJsonCustom for a custom resolver (one of the default resolvers or custom)
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddSpanJson();
}

AddSpanJson is the closest in behaviour compared to the default JSON.NET formatter; this uses the AspNetCoreDefaultResolver type.

Note: This clears the Formatter list; if you have other formatters (e.g. JSON Patch or XML) you need to readd them.

Custom Resolver

As each option is a concrete class it is infeasible to supply concrete classes for each possible option combination. To support a custom combination implement your own custom formatter resolvers.

public sealed class CustomResolver<TSymbol> : ResolverBase<TSymbol, CustomResolver<TSymbol>> where TSymbol : struct
{
    public CustomResolver() : base(new SpanJsonOptions
    {
        NullOption = NullOptions.ExcludeNulls,
        NamingConvention = NamingConventions.CamelCase,
        EnumOption = EnumOptions.Integer,
        ByteArrayOptions = ByteArrayOptions.Base64
    })
    {
    }
}

and pass this type just the same as e.g. ExcludeNullsCamelCaseResolver

TODO

  • Improve async deserialization/serialization: Find a way to do it streaming instead of buffering.
Product Compatible and additional computed target framework versions.
.NET 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 was computed.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (19)

Showing the top 5 NuGet packages that depend on SpanJson:

Package Downloads
SpanJson.AspNetCore.Formatter

SpanJson ASP.NET Core Formatter is the ASP.NET Core formatter package for SpanJson for ASP.NET Core 3.0+

Miki.Serialization.SpanJson

Package Description

Bundgaard.JsonRpcLib

C# DotNetCore 2.1+ Client/Server Json RPC library Using Span<T>, Memory<T> and IO pipelines

Eventso.Subscription.SpanJson

Eventso subscription JSON message deserializer using SpanJson library

BitbankDotNet

.NET wrapper for Bitbank.cc API.

GitHub repositories (3)

Showing the top 3 popular GitHub repositories that depend on SpanJson:

Repository Stars
dotnet/orleans
Cloud Native application framework for .NET
MessagePack-CSharp/MessagePack-CSharp
Extremely Fast MessagePack Serializer for C#(.NET, .NET Core, Unity, Xamarin). / msgpack.org[C#]
dotnet/corefxlab
This repo is for experimentation and exploring new ideas that may or may not make it into the main corefx repo.
Version Downloads Last updated
4.2.1 12,641 10/9/2024
4.2.0 34,785 7/29/2024
4.1.0 20,204 5/23/2024
4.0.1 385,261 7/13/2023
4.0.0 282,935 11/15/2022
3.3.1 111,158 8/19/2022
3.3.0 618 8/19/2022
3.2.4 5,970 7/28/2022
3.2.3 1,035 7/24/2022
3.2.2 133,069 4/15/2022
3.2.1 671 4/14/2022
3.2.0 202,457 12/14/2021
3.1.0 280,967 11/19/2020
3.1.0-rc1 352 11/16/2020
3.0.1 57,452 11/23/2019
3.0.0 5,174 9/23/2019
3.0.0-alpha-02 461 9/23/2019
3.0.0-alpha-01 364 9/7/2019
2.0.14 12,882 5/31/2019
2.0.13 819 5/30/2019
2.0.12 1,045 5/30/2019
2.0.11 868 5/21/2019
2.0.10 4,548 5/13/2019
2.0.9 5,163 4/11/2019
2.0.8 1,889 4/4/2019
2.0.7 19,188 3/6/2019
2.0.6 1,320 2/26/2019
2.0.5 803 2/25/2019
2.0.4 5,411 1/24/2019
2.0.3 1,031 1/24/2019
2.0.2 1,358 1/17/2019
2.0.1 13,686 1/2/2019
2.0.0 1,823 12/15/2018
2.0.0-rc6 615 12/9/2018
2.0.0-rc5 622 12/8/2018
2.0.0-rc4 679 12/8/2018
2.0.0-rc3 724 11/29/2018
2.0.0-rc2 656 11/27/2018
2.0.0-rc1 764 11/27/2018
1.3.3 990 11/26/2018
1.3.2 893 11/19/2018
1.3.1 916 11/9/2018
1.3.0 125,714 10/9/2018
1.2.5 1,295 9/21/2018
1.2.4 5,156 7/31/2018
1.2.3 919 7/23/2018
1.2.2 1,145 7/22/2018
1.2.1 992 7/18/2018
1.2.0 2,377 7/16/2018
1.1.0 1,121 7/14/2018
1.0.7 14,675 7/3/2018
1.0.6 983 7/3/2018
1.0.5 865 7/3/2018
1.0.4 871 7/2/2018
1.0.3 1,014 7/1/2018
1.0.2 1,000 7/1/2018
1.0.1 2,017 7/1/2018
1.0.0.2-womjlssg 954 7/1/2018
1.0.0.1 1,310 7/1/2018
1.0.0 1,492 7/1/2018
0.4.0 1,064 6/24/2018
0.3.0.7 1,044 6/23/2018
0.3.0.5 1,027 6/22/2018
0.3.0.4 1,100 6/20/2018
0.3.0.3 984 6/19/2018
0.3.0.2 1,010 6/19/2018
0.3.0.1 933 6/18/2018
0.3.0 991 6/17/2018
0.2.0.2 1,009 6/16/2018
0.2.0.1 911 6/15/2018
0.2.0 1,065 6/15/2018
0.1.0.5 1,069 6/5/2018
0.1.0.3 1,523 5/30/2018
0.1.0-rc2-9 821 5/23/2018
0.1.0-rc2-7 781 5/20/2018
0.1.0-rc2-5 2,388 5/13/2018
0.1.0-rc2-3 835 5/12/2018
0.1.0-rc2-1 840 5/12/2018
0.1.0-rc1-6 921 5/11/2018
0.1.0-rc1-5 1,039 5/10/2018
0.1.0-rc1-4 808 5/10/2018
0.1.0-rc1-3 803 5/8/2018
0.1.0-rc1-2 806 5/7/2018

Fix UTF16 Exponential Parsing for Decimal