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 });
}
}
有没有更好的方法?
这种方式的问题:
- 每个启用转换器的 class,都必须分叉并实现复制方法。
- 注释行不起作用,因为序列化器盲目地重新调用转换器和堆栈溢出。
- 我没有找到避免自递归的方法。
如果有办法强制调用默认转换,则可以避免自递归。
顺便说一句:
转换器上的 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;
}
优点:
- 序列化代码与序列化代码分开 类。
- 直截了当。避免摆弄过于复杂的 resolver/provider.
(过于复杂 => 解决方案对于一个简单的问题来说太复杂了)
缺点:
- 使用反射。
我对反射很好,因为它是 fork repo 和修改的快捷方式。
- .CanRead/.CanWrite 看起来很讨厌。
但是我没有想出更好的解决方案。 IMO 最好添加一些直接的回调。
我想以一种非侵入性的方式稍微调整序列化结果。
例如,我想要这个 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 });
}
}
有没有更好的方法?
这种方式的问题:
- 每个启用转换器的 class,都必须分叉并实现复制方法。
- 注释行不起作用,因为序列化器盲目地重新调用转换器和堆栈溢出。
- 我没有找到避免自递归的方法。
如果有办法强制调用默认转换,则可以避免自递归。
顺便说一句:
转换器上的 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;
}
优点:
- 序列化代码与序列化代码分开 类。
- 直截了当。避免摆弄过于复杂的 resolver/provider.
(过于复杂 => 解决方案对于一个简单的问题来说太复杂了)
缺点:
- 使用反射。
我对反射很好,因为它是 fork repo 和修改的快捷方式。 - .CanRead/.CanWrite 看起来很讨厌。
但是我没有想出更好的解决方案。 IMO 最好添加一些直接的回调。