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.Runtime.Serialization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
public partial class MyJsonClass
public MyJsonClass x { get; set; }
[JsonConverter(typeof(MyConverter))]
public partial class MyJsonClass
void OnSerializingMethod(StreamingContext context) => MyConverter.PushDisabled(false);
void OnSerializedMethod(StreamingContext context) => MyConverter.PopDisabled();
void OnDeserializingMethod(StreamingContext context) => MyConverter.PushDisabled(false);
void OnDeserializedMethod(StreamingContext context) => MyConverter.PopDisabled();
sealed class MyConverter : RecursiveConverterBase<MyJsonClass, MyConverter>
class DTO { public MyJsonClass MyJsonClass { get; set; } }
protected override void WriteJsonWithDefault(JsonWriter writer, object value, JsonSerializer serializer)
writer.WriteStartObject();
writer.WritePropertyName(nameof(DTO.MyJsonClass));
serializer.Serialize(writer, value);
protected override object ReadJsonWithDefault(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
using (var pushValue = PushDisabledUsing(true))
return serializer.Deserialize<DTO>(reader)?.MyJsonClass;
public abstract class RecursiveConverterBase<TValue, TConverter> : JsonConverter where TConverter : RecursiveConverterBase<TValue, TConverter>
static readonly ThreadLocal<Stack<bool>> disabledStack = new (() => new Stack<bool>());
public static StackExtensions.PushValue<bool> PushDisabledUsing(bool disable) => disabledStack.Value.PushUsing(disable);
public static void PushDisabled(bool disable)
disabledStack.Value.Push(disable);
public static void PopDisabled()
disabledStack.Value.Pop();
static bool Disabled => disabledStack.IsValueCreated && disabledStack.Value.TryPeek(out var disabled) && disabled;
static bool InSerialization => disabledStack.IsValueCreated && disabledStack.Value.Count > 0;
public override bool CanRead => !Disabled;
public override bool CanWrite => !Disabled;
public override bool CanConvert(Type objectType) => typeof(TValue).IsAssignableFrom(objectType);
protected abstract void WriteJsonWithDefault(JsonWriter writer, object value, JsonSerializer serializer);
public sealed override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
using (var pushValue = PushDisabledUsing(true))
WriteJsonWithDefault(writer, value, serializer);
protected abstract object ReadJsonWithDefault(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer);
public sealed override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
using (var pushValue = PushDisabledUsing(true))
return ReadJsonWithDefault(reader, objectType, existingValue, serializer);
public static class StackExtensions
public class PushValue<T> : IDisposable
public PushValue(T value, Stack<T> stack)
this.count = stack.Count;
while (stack.Count > count)
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
public static class MyJsonClassExtensions
public static int Depth(this MyJsonClass value) => value == null ? 0 : 1 + (value.x?.Depth()).GetValueOrDefault(0);
public static void Test()
var myClass = new MyJsonClass
var json = JsonConvert.SerializeObject(myClass, Formatting.Indented);
var root2 = JsonConvert.DeserializeObject<MyJsonClass>(json);
Assert.AreEqual(myClass.Depth(), root2.Depth());
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.Location.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries);
int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App");
if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2)
return assemblyPath[netCoreAppIndex + 1];