忽略一个序列化调用的 [JsonConverter] 属性



我做了一个自定义 Json.NET JsonConverter,其中包含如下代码:

public class MyConverter : JsonConverter 
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
.
.
.
serializer.Serialize(writer, value);
.
.
.
}
}
[JsonConverter(typeof(MyConverter))]
public class MyJsonClass { 
.
.
.
public MyContainer<MyJsonClass> x { get; set; }
.
.
.
}

我希望调用.Serialize方法在自定义MyConverter内时忽略 JsonConverter。基本上做我的自定义业务,然后回退到默认转换器。请注意,我不能在这里使用在递归下设置线程静态变量和设置的技巧(至少以我理解的方式),因为这个 JsonConverter 可以而且应该能够递归到自身,即在序列化MyContainer<MyJsonClass>时,其中可能包含应通过首先调用MyConverter自定义转换器进行序列化的MyJsonClass实例, 然后再次调用默认转换器。

您要做的是,当要序列化的数据模型是递归的时,从JsonConverter.WriteJson()方法中生成对象的默认序列化,因此将遇到相同类型的嵌套对象,并且需要使用转换器进行序列化。您已经正确得出结论,使用线程静态布尔变量禁用转换器(如本答案所示)以在使用 [JsonConvert()] 时 JSON.Net 抛出 StackOverflowException,将无法正确序列化这些子对象。

那么,在这种情况下,您有什么选择?

首先,您可以将线程静态bool转换为Stack<bool>,并推送一个标志以重新启用[OnSerializing]中的转换器,并在对象本身中[OnDeserializing]方法。 然后,该标志将在[OnSerialized][OnDeserialized]方法中弹出。 详情如下。

假设您的数据模型如下所示:

public partial class MyJsonClass 
{
public MyJsonClass x { get; set; }
}

出于某种原因,您希望在某个包装器对象中序列化MyJsonClass的每个实例,如下所示:

{ "MyJsonClass":{ /* The contents of the MyJsonClass instance */ }

然后,您可以使用以下支持推送和弹出Disabled状态的JsonConverter,按如下方式修改MyJsonClass

[JsonConverter(typeof(MyConverter))]
public partial class MyJsonClass 
{
[OnSerializing]
void OnSerializingMethod(StreamingContext context) => MyConverter.PushDisabled(false); // Re-enable the converter after we begin to serialize the object
[OnSerialized]
void OnSerializedMethod(StreamingContext context) => MyConverter.PopDisabled(); // Restore the converter to its previous state when serialization is complete
[OnDeserializing] 
void OnDeserializingMethod(StreamingContext context) => MyConverter.PushDisabled(false); // Re-enable the converter after we begin to deserialize the object
[OnDeserialized]
void OnDeserializedMethod(StreamingContext context) => MyConverter.PopDisabled(); // Restore the converter to its previous state when deserialization is complete
}
sealed class MyConverter : RecursiveConverterBase<MyJsonClass, MyConverter>
{
class DTO { public MyJsonClass MyJsonClass { get; set; } }
protected override void WriteJsonWithDefault(JsonWriter writer, object value, JsonSerializer serializer)
{
// Add your custom logic here.
writer.WriteStartObject();
writer.WritePropertyName(nameof(DTO.MyJsonClass));
serializer.Serialize(writer, value);
writer.WriteEndObject();
}
protected override object ReadJsonWithDefault(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
using (var pushValue = PushDisabledUsing(true))
{
// Add your custom logic here.
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)
{
if (InSerialization)
disabledStack.Value.Push(disable);
}
public static void PopDisabled()
{
if (InSerialization)
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); // Or typeof(TValue) == objectType if you prefer.
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
{
readonly Stack<T> stack;
readonly int count;
public PushValue(T value, Stack<T> stack)
{
this.stack = stack;
this.count = stack.Count;
stack.Push(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (stack != null)
{
while (stack.Count > count)
stack.Pop();
}
}
}
public static PushValue<T> PushUsing<T>(this Stack<T> stack, T value)
{
if (stack == null)
throw new ArgumentNullException();
return new PushValue<T>(value, stack);
}
}

现在,如果您有这样的MyJsonClass实例:

var myClass = new MyJsonClass
{
x = new MyJsonClass
{
x = new MyJsonClass { }
},
};

它将按如下方式序列化:

{
"MyJsonClass": {
"x": {
"MyJsonClass": {
"x": {
"MyJsonClass": {
"x": null
}
}
}
}
}
}

笔记:

  • 即使转换器状态被推入On[De]Serializing并在On[De]Serialized中弹出,如果在序列化过程中抛出异常,例如某种IOException,则有可能不会调用On[De]Serialized

    如果发生这种情况,disabledStack将无法正确还原,并且将来的序列化可能会损坏。

    为了防止这种情况,在JsonConverter.ReadJson()WriteJson()中,我们记住初始堆栈深度并将堆栈恢复到该深度,而不是简单地弹出禁用状态。 在序列化回调中,推送和弹出调用不执行任何操作,除非实际在JsonConverter方法中。

演示小提琴在这里。

其次,如果您正在写入中间JToken层次结构而不是直接写入传入JsonWriter writer,则可以使用此答案中的第二个更简单的解决方法JToken JsonExtensions.DefaultFromObject(this JsonSerializer serializer, object value)来 JSON.Net 在使用 [JsonConvert()] 时抛出 StackOverflowException

但是,您的问题表明您直接写信给传入的作者,因此这可能不适用。

第三,可以使用序列化程序的协定解析程序获取类型的JsonObjectContract,然后循环浏览属性并根据其协定信息手动序列化每个属性。 有关详细信息,请参阅 Daniel Müller 的答案,以JSON.Net 在使用 [JsonConvert()] 时抛出 StackOverflowException

相关内容

  • 没有找到相关文章

最新更新