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;
using Newtonsoft.Json.Linq;
public class JTokenConverterFactory : JsonConverterFactory
readonly Newtonsoft.Json.JsonSerializerSettings settings;
public JTokenConverterFactory() { }
public JTokenConverterFactory(Newtonsoft.Json.JsonSerializerSettings settings) => this.settings = settings;
public override bool CanConvert(Type typeToConvert) => typeof(JToken).IsAssignableFrom(typeToConvert);
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options)
var converterType = typeof(JTokenConverter<>).MakeGenericType(new [] { typeToConvert} );
return (JsonConverter)Activator.CreateInstance(converterType, new object [] { options, settings } );
class JTokenConverter<TJToken> : JsonConverter<TJToken> where TJToken : JToken
readonly JsonConverter<bool> boolConverter;
readonly JsonConverter<long> longConverter;
readonly JsonConverter<double> doubleConverter;
readonly JsonConverter<decimal> decimalConverter;
readonly JsonConverter<string> stringConverter;
readonly JsonConverter<DateTime> dateTimeConverter;
readonly Newtonsoft.Json.JsonSerializerSettings settings;
public override bool CanConvert(Type typeToConvert) => typeof(TJToken).IsAssignableFrom(typeToConvert);
public JTokenConverter(JsonSerializerOptions options, Newtonsoft.Json.JsonSerializerSettings settings)
boolConverter = (JsonConverter<bool>)options.GetConverter(typeof(bool));
stringConverter = (JsonConverter<string>)options.GetConverter(typeof(string));
longConverter = (JsonConverter<long>)options.GetConverter(typeof(long));
decimalConverter = (JsonConverter<decimal>)options.GetConverter(typeof(decimal));
doubleConverter = (JsonConverter<double>)options.GetConverter(typeof(double));
dateTimeConverter = (JsonConverter<DateTime>)options.GetConverter(typeof(DateTime));
this.settings = settings;
public override TJToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
using var doc = JsonDocument.ParseValue(ref reader);
using var ms = new MemoryStream();
using (var writer = new Utf8JsonWriter(ms))
using (var sw = new StreamReader(ms))
using (var jw = new Newtonsoft.Json.JsonTextReader(sw))
return Newtonsoft.Json.JsonSerializer.CreateDefault(settings).Deserialize<TJToken>(jw);
public override void Write(Utf8JsonWriter writer, TJToken value, JsonSerializerOptions options) =>
WriteCore(writer, value, options);
void WriteCore(Utf8JsonWriter writer, JToken value, JsonSerializerOptions options)
if (value == null || value.Type == JTokenType.Null)
case JValue jvalue when jvalue.GetType() != typeof(JValue):
using var ms = new MemoryStream();
using (var tw = new StreamWriter(ms, leaveOpen : true))
using (var jw = new Newtonsoft.Json.JsonTextWriter(tw))
using var doc = JsonDocument.Parse(ms);
case JValue jvalue when jvalue.Value is bool v:
boolConverter.WriteOrSerialize(writer, v, options);
case JValue jvalue when jvalue.Value is string v:
stringConverter.WriteOrSerialize(writer, v, options);
case JValue jvalue when jvalue.Value is long v:
longConverter.WriteOrSerialize(writer, v, options);
case JValue jvalue when jvalue.Value is decimal v:
decimalConverter.WriteOrSerialize(writer, v, options);
case JValue jvalue when jvalue.Value is double v:
doubleConverter.WriteOrSerialize(writer, v, options);
case JValue jvalue when jvalue.Value is DateTime v:
dateTimeConverter.WriteOrSerialize(writer, v, options);
JsonSerializer.Serialize(writer, jvalue.Value, options);
writer.WriteStartArray();
foreach (var item in array)
WriteCore(writer, item, options);
writer.WriteStartObject();
foreach (var p in obj.Properties())
writer.WritePropertyName(p.Name);
WriteCore(writer, p.Value, options);
public static class JsonExtensions
public static void WriteOrSerialize<T>(this JsonConverter<T> converter, Utf8JsonWriter writer, T value, JsonSerializerOptions options)
converter.Write(writer, value, options);
JsonSerializer.Serialize(writer, value, options);
public JObject Data { get; set; }
public static void Test()
var inputJson = @"{""value"":[[null,true,false,1010101,1010101.10101,""hello"",""𩸽"",""\uD867\uDE3D"",""2009-02-15T00:00:00Z"",""\uD867\uDE3D\u0022\\/\b\f\n\r\t\u0121""]]}";
var model = new Model { Data = JObject.Parse(inputJson) };
var options = new JsonSerializerOptions
Converters = { new JTokenConverterFactory() },
var outputJson = JsonSerializer.Serialize(model, options);
Console.WriteLine(outputJson);
Assert.IsTrue(JToken.DeepEquals(JToken.Parse(inputJson), JToken.Parse(outputJson)[nameof(Model.Data)]));
foreach (var json in GetJson())
Test(JToken.Parse(json));
static void TestRaw(string originalJson)
var options = new JsonSerializerOptions
Converters = { new JTokenConverterFactory() },
ReadCommentHandling = JsonCommentHandling.Skip,
var raw = new JRaw(originalJson);
SerializeAndAssert(new JRaw(originalJson), options);
static void Test(JToken root)
var options = new JsonSerializerOptions
Converters = { new JTokenConverterFactory() },
var json2 = SerializeAndAssert(root, options);
Console.WriteLine(json2);
var root2 = JsonSerializer.Deserialize<JToken>(json2, options);
Assert.IsTrue(AreEqual(root, root2));
static string SerializeAndAssert(JToken root, JsonSerializerOptions options)
var json1 = JsonSerializer.Serialize(root, options);
var json2 = JsonSerializer.Serialize(root, root.GetType(), options);
Assert.IsTrue(AreEqual(root, JToken.Parse(json1)), root?.ToString());
Assert.AreEqual(json1, json2, "json1 == json2");
static bool IsNull(JToken token)
if (token.Type == JTokenType.Null)
static bool AreEqual(JToken token1, JToken token2)
token1 = JToken.Parse(token1.ToString());
token2 = JToken.Parse(token2.ToString());
if (IsNull(token1) && IsNull(token2))
return JToken.DeepEquals(token1, token2);
public static IEnumerable<string> GetJson()
@"""2009-02-15T00:00:00Z""",
@"""\uD867\uDE3D\""\\\/\b\f\n\r\t\u0121""",
""title"": ""example glossary"",
""GlossTerm"": ""Standard Generalized Markup Language"",
""Abbrev"": ""ISO 8879:1986"",
""para"": ""A meta-markup language, used to create markup languages such as DocBook."",
""GlossSeeAlso"": [""GML"", ""XML""]
{""value"": ""New"", ""onclick"": ""CreateNewDoc()""},
{""value"": ""Open"", ""onclick"": ""OpenDoc()""},
{""value"": ""Close"", ""onclick"": ""CloseDoc()""}
var primitivesAndExamples = primitives.Concat(examples);
var arrays = primitivesAndExamples.Select(s => "[[" + s + ", [" + s + "]]]").Concat(new [] { "[[" + string.Join(',', primitives) +"]]"});
var objects = arrays.Select(s => "{" + "\"value\":" + s + "}");
return primitivesAndExamples.Concat(arrays).Concat(objects);
public static void Main()
Console.WriteLine("Environment version: {0} ({1})", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , GetNetCoreVersion());
Console.WriteLine("System.Text.Json version: " + 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.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];