using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Text.Json.Serialization;
public static partial class JsonExtensions
public static TValue Deserialize<TValue>(IEnumerable<string> buffers, JsonSerializerOptions options = null)
return Deserialize<TValue>(ToByteArrayChunks(buffers));
public static TValue Deserialize<TValue>(IEnumerable<byte []> buffers, JsonSerializerOptions options = null)
var sequence = ReadOnlySequenceFactory.Create(buffers);
var reader = new Utf8JsonReader(sequence, options.GetReaderOptions());
return JsonSerializer.Deserialize<TValue>(ref reader, options);
public static JsonReaderOptions GetReaderOptions(this JsonSerializerOptions options)
return new JsonReaderOptions();
return new JsonReaderOptions
AllowTrailingCommas = options.AllowTrailingCommas,
CommentHandling = options.ReadCommentHandling,
MaxDepth = options.MaxDepth
static readonly Encoding encoding = new UTF8Encoding(false);
static IEnumerable<byte []> ToByteArrayChunks(IEnumerable<string> buffers)
var encoder = encoding.GetEncoder();
foreach (var s in buffers)
ReadOnlySpan<char> charSpan = s;
var count = encoder.GetByteCount(charSpan, false);
var bytes = new byte[count];
Span<byte> byteSpan = bytes;
encoder.GetBytes(charSpan, byteSpan, false);
public static class ReadOnlySequenceFactory
class ReadOnlyMemorySegment<T> : ReadOnlySequenceSegment<T>
public static ReadOnlySequence<T> Create(IEnumerable<ReadOnlyMemory<T>> buffers)
ReadOnlyMemorySegment<T> first = null;
ReadOnlyMemorySegment<T> current = null;
foreach (var buffer in buffers)
var next = new ReadOnlyMemorySegment<T> { Memory = buffer };
next.RunningIndex = current.RunningIndex + current.Memory.Length;
first = current = new ReadOnlyMemorySegment<T>();
return new ReadOnlySequence<T>(first, 0, current, current.Memory.Length);
public static ReadOnlySequence<T> Create<T>(IEnumerable<T []> buffers)
return ReadOnlyMemorySegment<T>.Create(buffers.Select(b => new ReadOnlyMemory<T>(b)));
public int Value { get; set; }
const string hokketonian = "\U00029E3D\u0302";
public static void Test()
static void Test(int count, bool print)
var jsonResults = GetJson(count).ToList();
Console.WriteLine("\nInput JSON:");
foreach (var s in jsonResults)
var list = JsonExtensions.Deserialize<List<RootObject>>(jsonResults);
Console.WriteLine("Re-serialized JSON:");
Console.WriteLine(JsonSerializer.Serialize(list, new JsonSerializerOptions { WriteIndented = true }));
Assert.AreEqual(count, list.Count);
Assert.IsTrue(Enumerable.Range(0, count).SequenceEqual(list.Select(i => i.Value)));
static void TestSurrogate()
var expected = string.Concat(Enumerable.Repeat(hokketonian, count));
Console.Write("Expected JSON string: ");
Console.WriteLine(expected);
var jsonResults = GetJsonWithSurrogateStrings(hokketonian, count);
var s = JsonExtensions.Deserialize<string>(jsonResults);
Console.Write("Actual JSON string : ");
Assert.AreEqual(expected, s);
static IEnumerable<string> GetJson(int count)
return new [] { "", "[" }
.Concat(Enumerable.Range(0, count).Select(i => JsonSerializer.Serialize(new RootObject { Value = i }) + (i < count - 1 ? "," : "")))
static IEnumerable<string> GetJsonWithSurrogateStrings(string input, int count)
var quote = new [] { "\"" };
return quote.Concat(Enumerable.Repeat(input, 5).SelectMany(s => s).Select(c => c.ToString())).Concat(quote);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("{0} version: {1}", typeof(JsonSerializer).Assembly.GetName().Name, typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public static string GetNetCoreVersion()
var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly;
var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];