using System.Collections.Generic;
using BF = System.Reflection.BindingFlags;
public static void Main() {
var settingsStock = new JsonSerializerSettings { Formatting = Formatting.Indented, };
var jsonRef = JsonConvert.SerializeObject(foo, settingsStock);
Console.WriteLine($"Default:\n{jsonRef}");
var settingsMutating = new JsonSerializerSettings {
Formatting = Formatting.Indented,
Converters = new List<JsonConverter> { new demo_serializer.FooConverter() },
var jsonMutated = JsonConvert.SerializeObject(foo, settingsMutating);
Console.WriteLine($"Mutated:\n{jsonMutated}");
var fooRestored = JsonConvert.DeserializeObject<Foo>(jsonMutated, settingsMutating);
var jsonRestored = JsonConvert.SerializeObject(fooRestored, settingsStock);
Console.WriteLine($"Restored:\n{jsonRestored}");
if (jsonRestored.Length != jsonRef.Length)
Console.WriteLine($"len mismatch {jsonRestored.Length} vs {jsonRef.Length}");
for (int i = 0; i < jsonRef.Length; i++) {
if (jsonRestored[i] != jsonRef[i]) {
Console.WriteLine($"mismatch at {i}, {jsonRestored[i].ToString()} vs {jsonRef[i].ToString()}, context=\n{jsonRef.Substring(0, i - 1)}");
if (jsonRestored != jsonRef)
throw new ApplicationException();
namespace demo_serializer {
public class FooConverter : sample.InPlaceMutateConverter {
public override bool CanConvert(Type objectType) { return objectType == typeof(demo.Foo); }
protected override object MutateOnRead(object value) { return MutateAfterDeserialize((demo.Foo)value); }
protected override object MutateOnWrite(object value, out bool typeWillMatchSelf) {
typeWillMatchSelf = true;
return MutateBeforeSerialize((demo.Foo)value);
demo.Foo MutateAfterDeserialize(demo.Foo foo) {
foo.va = (foo.va + 1) / 100;
demo.Foo MutateBeforeSerialize(demo.Foo foo) {
return new demo.Foo { name = foo.name, va = foo.va * 100 };
foo.va = foo.va * 100 - 1;
public class SerializerContext {
public void peekWriter(JsonSerializer serializerProxy) {
if (writer != null) return;
if (verifySerializerProxy(serializerProxy)) {
writer = serializerProxy.GetType().GetField("_serializerWriter", BF.Instance | BF.NonPublic).GetValue(serializerProxy);
objectsStack = (List<object>)writer.GetType().GetField("_serializeStack", BF.Instance | BF.NonPublic).GetValue(writer);
public void peekReader(JsonSerializer serializerProxy) {
if (reader != null) return;
if (verifySerializerProxy(serializerProxy)) {
reader = serializerProxy.GetType().GetField("_serializerReader", BF.Instance | BF.NonPublic).GetValue(serializerProxy);
public bool verifySerializerProxy(JsonSerializer serializerProxy) {
var assembly = typeof(JsonSerializer).Assembly;
var serializerProxyType = assembly.GetType("Newtonsoft.Json.Serialization.JsonSerializerProxy");
return serializerProxyType != null && serializerProxyType.IsInstanceOfType(serializerProxy);
public object reader = null!;
public object writer = null!;
public List<object> objectsStack = null!;
public abstract class InPlaceMutateConverter : JsonConverter {
public override bool CanRead {
avoidNextRecurse = false;
public override bool CanWrite {
avoidNextRecurse = false;
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
serializerContext.peekReader(serializer);
Type newObjectType = GetOutputTypeOnRead(objectType, existingValue, out avoidNextRecurse);
object value = serializer.Deserialize(reader, newObjectType)!;
return MutateOnRead(value);
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
if (objectsStack == null) {
serializerContext.peekWriter(serializer);
objectsStack = serializerContext.objectsStack;
object newValue = MutateOnWrite(value, out avoidNextRecurse);
objectsStack.Remove(objectsStack.Count - 1);
serializer.Serialize(writer, newValue);
protected virtual Type GetOutputTypeOnRead(Type objectType, object existingValue, out bool typeWillMatchSelf) {
typeWillMatchSelf = true;
protected virtual object MutateOnRead(object value) { return value; }
protected virtual object MutateOnWrite(object value, out bool typeWillMatchSelf) {
typeWillMatchSelf = true;
protected bool canRead = true;
protected bool canWrite = true;
protected SerializerContext serializerContext = new SerializerContext();
List<object> objectsStack = null!;
protected bool avoidNextRecurse = false;