using System.Collections;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using System.Text.Json.Nodes;
[JsonDerivedType(typeof(DerivedClass), "derived")]
public abstract class BaseClass
public class DerivedClass : BaseClass
public string? Whatever { get; set; }
public static void Test()
var jsonWorks = "{\"$type\": \"derived\", \"whatever\": \"Bar\"}";
var jsonBreaks = "{\"whatever\": \"Bar\", \"$type\": \"derived\"}";
Console.WriteLine(JsonNode.Parse(jsonBreaks).MoveMetadataToBeginning());
var obj1 = JsonNode.Parse(jsonWorks).MoveMetadataToBeginning().Deserialize<BaseClass>();
var obj2 = JsonNode.Parse(jsonBreaks).MoveMetadataToBeginning().Deserialize<BaseClass>();
var obj3 = JsonNode.Parse(jsonNull).MoveMetadataToBeginning().Deserialize<BaseClass>();
public static partial class JsonExtensions
const string Ref = "$ref";
static bool DefaultIsTypeDiscriminator(string s) => s == "$type";
public static TJsonNode? MoveMetadataToBeginning<TJsonNode>(this TJsonNode? node) where TJsonNode : JsonNode => node.MoveMetadataToBeginning(DefaultIsTypeDiscriminator);
public static TJsonNode? MoveMetadataToBeginning<TJsonNode>(this TJsonNode? node, Predicate<string> isTypeDiscriminator) where TJsonNode : JsonNode
ArgumentNullException.ThrowIfNull(isTypeDiscriminator);
foreach (var n in node.DescendantsAndSelf().OfType<JsonObject>())
var properties = n.ToLookup(p => isTypeDiscriminator(p.Key) || p.Key == Id || p.Key == Ref);
var newProperties = properties[true].Concat(properties[false]).ToList();
newProperties.ForEach(p => n.Add(p));
public static IEnumerable<JsonNode?> Descendants(this JsonNode? root) => root.DescendantsAndSelf(false);
public static IEnumerable<JsonNode?> DescendantsAndSelf(this JsonNode? root, bool includeSelf = true) =>
root.DescendantItemsAndSelf(includeSelf).Select(i => i.node);
public static IEnumerable<(JsonNode? node, int? index, string? name, JsonNode? parent)> DescendantItemsAndSelf(this JsonNode? root, bool includeSelf = true) =>
RecursiveEnumerableExtensions.Traverse(
(node: root, index: (int?)null, name: (string?)null, parent: (JsonNode?)null),
JsonObject o => o.AsDictionary().Select(p => (p.Value, (int?)null, p.Key.AsNullableReference(), i.node.AsNullableReference())),
JsonArray a => a.Select((item, index) => (item, index.AsNullableValue(), (string?)null, i.node.AsNullableReference())),
_ => i.ToEmptyEnumerable(),
static IEnumerable<T> ToEmptyEnumerable<T>(this T item) => Enumerable.Empty<T>();
static T? AsNullableReference<T>(this T item) where T : class => item;
static Nullable<T> AsNullableValue<T>(this T item) where T : struct => item;
static IDictionary<string, JsonNode?> AsDictionary(this JsonObject o) => o;
public static partial class RecursiveEnumerableExtensions
public static IEnumerable<T> Traverse<T>(
Func<T, IEnumerable<T>> children, bool includeSelf = true)
var stack = new Stack<IEnumerator<T>>();
stack.Push(children(root).GetEnumerator());
var enumerator = stack.Peek();
if (!enumerator.MoveNext())
yield return enumerator.Current;
stack.Push(children(enumerator.Current).GetEnumerator());
foreach (var enumerator in stack)
public static void Main()
Console.WriteLine("Environment version: {0} ({1}), {2}", System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription , Environment.Version, Environment.OSVersion);
Console.WriteLine("System.Text.Json version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");