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 JToken = Newtonsoft.Json.Linq.JToken;
public class JsonElementComparer : IEqualityComparer<JsonElement>
public JsonElementComparer() : this(-1) { }
public JsonElementComparer(int maxHashDepth) => this.MaxHashDepth = maxHashDepth;
int MaxHashDepth { get; } = -1;
#region IEqualityComparer<JsonElement> Members
public bool Equals(JsonElement x, JsonElement y)
if (x.ValueKind != y.ValueKind)
case JsonValueKind.False:
case JsonValueKind.Undefined:
case JsonValueKind.Number:
return x.GetRawText() == y.GetRawText();
case JsonValueKind.String:
return x.GetString() == y.GetString();
case JsonValueKind.Array:
return x.EnumerateArray().SequenceEqual(y.EnumerateArray(), this);
case JsonValueKind.Object:
var xPropertiesUnsorted = x.EnumerateObject().ToList();
var yPropertiesUnsorted = y.EnumerateObject().ToList();
if (xPropertiesUnsorted.Count != yPropertiesUnsorted.Count)
var xProperties = xPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal);
var yProperties = yPropertiesUnsorted.OrderBy(p => p.Name, StringComparer.Ordinal);
foreach (var (px, py) in xProperties.Zip(yProperties))
if (!Equals(px.Value, py.Value))
throw new JsonException(string.Format("Unknown JsonValueKind {0}", x.ValueKind));
public int GetHashCode(JsonElement obj)
var hash = new HashCode();
ComputeHashCode(obj, ref hash, 0);
return hash.ToHashCode();
void ComputeHashCode(JsonElement obj, ref HashCode hash, int depth)
case JsonValueKind.False:
case JsonValueKind.Undefined:
case JsonValueKind.Number:
hash.Add(obj.GetRawText());
case JsonValueKind.String:
hash.Add(obj.GetString());
case JsonValueKind.Array:
if (depth != MaxHashDepth)
foreach (var item in obj.EnumerateArray())
ComputeHashCode(item, ref hash, depth+1);
hash.Add(obj.GetArrayLength());
case JsonValueKind.Object:
foreach (var property in obj.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
if (depth != MaxHashDepth)
ComputeHashCode(property.Value, ref hash, depth+1);
throw new JsonException(string.Format("Unknown JsonValueKind {0}", obj.ValueKind));
public static void Test()
var comparer = new JsonElementComparer();
foreach (var test in GetJson())
var referenceJson = test.Json;
using var doc1 = JsonDocument.Parse(referenceJson);
Console.WriteLine(doc1.RootElement.ToString());
Assert.IsTrue(comparer.Equals(doc1.RootElement, doc1.RootElement));
Assert.AreEqual(comparer.GetHashCode(doc1.RootElement), comparer.GetHashCode(doc1.RootElement), "hash codes");
foreach (var json in test.IdenticalJson)
using var doc2 = JsonDocument.Parse(json);
Console.WriteLine(" should equal: " + doc2.RootElement.ToString());
Assert.IsTrue(comparer.Equals(doc1.RootElement, doc2.RootElement));
Assert.AreEqual(comparer.GetHashCode(doc1.RootElement), comparer.GetHashCode(doc2.RootElement), "hash codes");
foreach (var json in test.DifferentJson)
using var doc2 = JsonDocument.Parse(json);
Console.WriteLine(" should differ: " + doc2.RootElement.ToString());
Assert.IsFalse(comparer.Equals(doc1.RootElement, doc2.RootElement));
Assert.AreNotEqual(comparer.GetHashCode(doc1.RootElement), comparer.GetHashCode(doc2.RootElement), "hash codes");
var json2 = @"""\u0061""";
Assert.IsTrue(JsonSerializer.Deserialize<string>(json1) == JsonSerializer.Deserialize<string>(json2));
using var doc1 = JsonDocument.Parse(json1);
using var doc2 = JsonDocument.Parse(json2);
Assert.IsTrue(JToken.DeepEquals(JToken.Parse("1.0"), JToken.Parse("1.00")));
static IEnumerable<JsonTestCase> GetJson()
Json = @"{""zzz"" : 1.0, ""Value"":""a"", ""Value"" : ""b""}",
@"{""zzz"" : 1.0, ""Value"":""a"", ""Value"" : ""b""}",
@"{""Value"":""a"", ""Value"" : ""b"", ""zzz"" : 1.0 }",
@"{""zzz"" : 1.0, ""Value"":""\u0061"", ""Value"" : ""b""}",
@"{""zzz"" : 1.00, ""Value"":""a"", ""Value"" : ""b""}",
@"{""zzz"" : 1.0, ""Value"":""b"", ""Value"" : ""a""}",
@"{""zzz"" : 1.0, ""Value"":""b"", ""Value"" : ""a""}",
@"{""zzz"" : 11, ""Value"":""a"", ""Value"" : ""b""}",
@"{""Zzz"" : 1.0, ""Value"":""a"", ""Value"" : ""b""}",
Json = @"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.1",
IdenticalJson = new string []
@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.1",
@"111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111.2",
return tests.Concat(tests.Select(t => ToArray(t)));
static JsonTestCase ToArray(JsonTestCase test)
Json = ToArray(test.Json),
IdenticalJson = test.IdenticalJson.Select(j => ToArray(j)).ToArray(),
DifferentJson = new [] { test.Json }.Concat(test.IdenticalJson).Concat(test.DifferentJson.Select(j => ToArray(j))).ToArray(),
static string ToArray(string s)
return "[" + s + ", 0, true, false, null, 0.3, " + s + "]";
public string Json { get; set; }
public string [] IdenticalJson { get; set; }
public string [] DifferentJson { get; set; }
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];