Newtonsoft JSON converter serialization: writeJson()中,如何修改value避免recurse into self?

Newtonsoft JSON converter serialzation: in writeJson(), how to modify value and avoid recurse into self?

我想以一种非侵入性的方式稍微调整序列化结果。
例如,我想要这个 class:

class A { int va; }

改成这样{ va: value } -> { va: value * 2 }
所以我试着做一个转换器,但我发现的唯一方法是这样的:

    [JsonConverter(typeof(NoConverter))]
    class B : A { }
    public class MyConverter : JsonConverter<A> {
        public override void WriteJson(JsonWriter writer, A obj, JsonSerializer serializer) {
            // serializer.Serialize(writer, obj);
            // serializer.Serialize(writer, new A { va = obj.va * 2 });
            serializer.Serialize(writer, new B { va = obj.va * 2 });
        }
    }

有没有更好的方法?

这种方式的问题:

  1. 每个启用转换器的 class,都必须分叉并实现复制方法。
  2. 注释行不起作用,因为序列化器盲目地重新调用转换器和堆栈溢出。
  3. 我没有找到避免自递归的方法。
    如果有办法强制调用默认转换,则可以避免自递归。

顺便说一句:
转换器上的 Newtonsoft 示例已过时,回购中的测试没有帮助。

多亏了这个 SO post 的 NoConverter,我至少有了一个可行的方法。
Why Json.net does not use customized IsoDateTimeConverter?

这个 SO 很有趣,但不能解决我的问题 - 我需要调用默认转换例程,它不是转换器。

以下应该适用于序列化。

class A 
{ 
    [JsonIgnore]  
    public int Va; 
    
    [JsonProperty(PropertyName = "Va")]
    private int VaOutput { get { return Va *2; } }
}

所以你把原来的属性标记为[JsonIgnore]

然后您定义一个新的私有 属性,其中 returns 您更改的值 (*2) 没有 setter。 这被赋予 属性 与您原来的名称相匹配的名称。

另一种方法是创建另一个看起来相同的 class 'B',接受 Class A 作为构造函数的输入,设置所有值并根据需要更改它们.然后你序列化这个 class 而不是原来的

我最终使用了这样的解决方案。
https://dotnetfiddle.net/OPoqbs.

处的完整示例
    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!; // internal Newtonsoft.Json.Serialization.JsonSerializerInternalReader
        public object writer = null!; // internal Newtonsoft.Json.Serialization.JsonSerializerInternalWriter
        public List<object> objectsStack = null!; // JsonSerializerInternalWriter._serializeStack
    }
    
    // In-place mutate/transform converter. Useful if new value maybe in same type, or maybe same object.
    public abstract class InPlaceMutateConverter : JsonConverter {
        public override bool CanRead {
            get {
                // Avoid next recurse to self
                if (avoidNextRecurse) {
                    avoidNextRecurse = false;
                    return false;
                } else {
                    return canRead;
                }
            }
        }

        public override bool CanWrite {
            get {
                // Avoid next recurse to self
                if (avoidNextRecurse) {
                    avoidNextRecurse = false;
                    return false;
                } else {
                    return canWrite;
                }
            }
        }

        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);
            // To defeat circular check
            if (newValue == value)
                objectsStack.Remove(objectsStack.Count - 1);
            serializer.Serialize(writer, newValue);
            // Restore stack
            if (newValue == value)
                objectsStack.Add(value);
        }

        protected virtual Type GetOutputTypeOnRead(Type objectType, object existingValue, out bool typeWillMatchSelf) {
            typeWillMatchSelf = true;
            return objectType;
        }
        protected virtual object MutateOnRead(object value) { return value; }
        // typeWillMatchSelf should be true, if converter will recurse to self: value type will still hit self.
        protected virtual object MutateOnWrite(object value, out bool typeWillMatchSelf) {
            typeWillMatchSelf = true;
            return value;
        }

        protected bool canRead = true;
        protected bool canWrite = true;
        protected SerializerContext serializerContext = new SerializerContext();
        List<object> objectsStack = null!;
        protected bool avoidNextRecurse = false;
    }

优点:

  1. 序列化代码与序列化代码分开 类。
  2. 直截了当。避免摆弄过于复杂的 resolver/provider.
    (过于复杂 => 解决方案对于一个简单的问题来说太复杂了)

缺点:

  1. 使用反射。
    我对反射很好,因为它是 fork repo 和修改的快捷方式。
  2. .CanRead/.CanWrite 看起来很讨厌。
    但是我没有想出更好的解决方案。 IMO 最好添加一些直接的回调。