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 (JTokenWriter newtonsoftWriter = new JTokenWriter())
ReadCore(ref reader, newtonsoftWriter, options, typeof(TJToken), Newtonsoft.Json.JsonSerializer.CreateDefault(settings));
return (TJToken)newtonsoftWriter.Token;
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);
void ReadArrayCore(ref Utf8JsonReader reader, Newtonsoft.Json.JsonWriter newtonsoftWriter, JsonSerializerOptions options, Newtonsoft.Json.JsonSerializer newtonsoftSerializer)
if (reader.TokenType != JsonTokenType.StartArray)
throw new JsonException();
newtonsoftWriter.WriteStartArray();
switch (reader.TokenType)
ReadCore(ref reader, newtonsoftWriter, options, typeof(JToken), newtonsoftSerializer);
case JsonTokenType.EndArray:
newtonsoftWriter.WriteEndArray();
throw new JsonException();
void ReadObjectCore(ref Utf8JsonReader reader, Newtonsoft.Json.JsonWriter newtonsoftWriter, JsonSerializerOptions options, Newtonsoft.Json.JsonSerializer newtonsoftSerializer)
if (reader.TokenType != JsonTokenType.StartObject)
throw new JsonException();
newtonsoftWriter.WriteStartObject();
switch (reader.TokenType)
case JsonTokenType.PropertyName:
var name = reader.GetString();
throw new JsonException();
newtonsoftWriter.WritePropertyName(name);
ReadCore(ref reader, newtonsoftWriter, options, typeof(JToken), newtonsoftSerializer);
case JsonTokenType.EndObject:
newtonsoftWriter.WriteEndObject();
throw new JsonException();
throw new JsonException();
static void CheckType(Type extectedType, Type actualType, JsonTokenType tokenType)
if (!extectedType.IsAssignableFrom(actualType))
throw new JsonException(string.Format("Expected type {0} cannot be created from token {1}", extectedType, tokenType));
void ReadCore(ref Utf8JsonReader reader, Newtonsoft.Json.JsonWriter newtonsoftWriter, JsonSerializerOptions options, Type extectedType, Newtonsoft.Json.JsonSerializer newtonsoftSerializer)
var t = reader.TokenType;
switch (reader.TokenType)
case JsonTokenType.Comment:
CheckType(extectedType, typeof(JValue), reader.TokenType);
newtonsoftWriter.WriteComment(reader.GetString());
case JsonTokenType.False:
CheckType(extectedType, typeof(JValue), reader.TokenType);
newtonsoftWriter.WriteValue(false);
CheckType(extectedType, typeof(JValue), reader.TokenType);
newtonsoftWriter.WriteValue(true);
CheckType(extectedType, typeof(JValue), reader.TokenType);
newtonsoftWriter.WriteNull();
case JsonTokenType.String:
CheckType(extectedType, typeof(JValue), reader.TokenType);
using var newtonsoftReader = new Newtonsoft.Json.JsonTextReader(new StringReader(Newtonsoft.Json.JsonConvert.ToString(reader.GetString())));
newtonsoftSerializer.Deserialize<JValue>(newtonsoftReader).WriteTo(newtonsoftWriter);
case JsonTokenType.Number:
CheckType(extectedType, typeof(JValue), reader.TokenType);
if (reader.TryGetInt64(out var l))
newtonsoftWriter.WriteValue(l);
using var doc = JsonDocument.ParseValue(ref reader);
using var newtonsoftReader = new Newtonsoft.Json.JsonTextReader(new StringReader(doc.RootElement.ToString()));
newtonsoftSerializer.Deserialize<JValue>(newtonsoftReader).WriteTo(newtonsoftWriter);
case JsonTokenType.StartArray:
CheckType(extectedType, typeof(JArray), reader.TokenType);
ReadArrayCore(ref reader, newtonsoftWriter, options, newtonsoftSerializer);
case JsonTokenType.StartObject:
CheckType(extectedType, typeof(JObject), reader.TokenType);
ReadObjectCore(ref reader, newtonsoftWriter, options, newtonsoftSerializer);
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), "AreEqual(root, root2)");
Console.WriteLine("Failed with root2:");
Console.WriteLine(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];