当前位置:  开发笔记 > 编程语言 > 正文

序列化时JSON.NET StackOverflowException

如何解决《序列化时JSON.NETStackOverflowException》经验,为你挑选了1个好方法。

当我尝试序列化具有类似结构的对象时,我的C#程序正在运行StackOverflowException:

对象具有互相引用的成员

无法尝试捕获(idk为什么)

如果计数设置为6500以下(可能因计算机而异),则表示已成功序列化

下面的示例代码:

class Chacha
{
    public Chacha NextChacha { get; set; }
}    
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
    TypeNameHandling = TypeNameHandling.All,
    PreserveReferencesHandling = PreserveReferencesHandling.Objects,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};

static void Main(string[] args)
{
        int count = 15000;

        Chacha[] steps = new Chacha[count];
        steps[0] = new Chacha();

        for (int i = 1; i < count; i++)
        {
            steps[i] = new Chacha();
            steps[i-1].NextChacha = steps[i];
        }

        string serSteps = JsonConvert.SerializeObject(steps, Settings);
}

JSON.NET版本是:9.0.1
.NET Framework:4.5.2有什么
解决方案如何序列化此结构?

欢迎任何帮助或建议。谢谢



1> dbc..:

出现stackoverflow异常的原因是Json.NET是一个递归的单遍树或图序列化程序,PreserveReferencesHandling.Objects启用该功能后,它将始终序列化每个对象的第一次出现。您已经构造了15,000个元素Chacha []数组,以便第一个条目是包含顺序连接的所有其他项目的链接列表的开头。Json.NET将尝试通过15,000个递归级别将其序列化为嵌套的JSON对象(深度为15,000个级别),从而使过程中的堆栈溢出。

因此,您需要做的是将整个链接表仅写在列表的开头,作为JSON数组。但是,不幸的是,Json.NET还是基于契约的序列化器,这意味着无论嵌套深度是多少,只要遇到给定类型的对象,Json.NET都将尝试写入相同的属性。因此,向对象添加Chacha[] NextChachaList属性Chacha无济于事,因为它将在每个级别写入。取而代之的是,有必要创建一个相当复杂的定制JsonConverter,该定制以线程安全的方式跟踪序列化深度,并且仅在顶层写入链接列表。以下是窍门:

class ChachaConverter : LinkedListItemConverter
{
    protected override bool IsNextItemProperty(JsonProperty member)
    {
        return member.UnderlyingName == "NextChacha"; // Use nameof(Chacha.NextChacha) in latest c#
    }
}

public abstract class LinkedListItemConverter : JsonConverter where T : class
{
    const string refProperty = "$ref";
    const string idProperty = "$id";
    const string NextItemListProperty = "nextItemList";

    [ThreadStatic]
    static int level;

    // Increments the nesting level in a thread-safe manner.
    int Level { get { return level; } set { level = value; } }

    public override bool CanConvert(Type objectType)
    {
        return typeof(T).IsAssignableFrom(objectType);
    }

    protected abstract bool IsNextItemProperty(JsonProperty member);

    List GetNextItemList(object value, JsonObjectContract contract)
    {
        var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
        List list = null;
        for (var item = (T)property.ValueProvider.GetValue(value); item != null; item = (T)property.ValueProvider.GetValue(item))
        {
            if (list == null)
                list = new List();
            list.Add(item);
        }
        return list;
    }

    void SetNextItemLinks(object value, List list, JsonObjectContract contract)
    {
        var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
        if (list == null || list.Count == 0)
            return;
        var previous = value;
        foreach (var next in list)
        {
            if (next == null)
                continue;
            property.ValueProvider.SetValue(previous, next);
            previous = next;
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        using (new PushValue(Level + 1, () => Level, (old) => Level = old))
        {
            writer.WriteStartObject();

            if (serializer.ReferenceResolver.IsReferenced(serializer, value))
            {
                writer.WritePropertyName(refProperty);
                writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));
            }
            else
            {
                writer.WritePropertyName(idProperty);
                writer.WriteValue(serializer.ReferenceResolver.GetReference(serializer, value));

                var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(value.GetType());

                // Write the data properties (if any).
                foreach (var property in contract.Properties
                    .Where(p => p.Readable && !p.Ignored && (p.ShouldSerialize == null || p.ShouldSerialize(value))))
                {
                    if (IsNextItemProperty(property))
                        continue;
                    var propertyValue = property.ValueProvider.GetValue(value);
                    if (propertyValue == null && serializer.NullValueHandling == NullValueHandling.Ignore)
                        continue;
                    writer.WritePropertyName(property.PropertyName);
                    serializer.Serialize(writer, propertyValue);
                }

                if (Level == 1)
                {
                    // Write the NextItemList ONLY AT THE TOP LEVEL
                    var nextItems = GetNextItemList(value, contract);
                    if (nextItems != null)
                    {
                        writer.WritePropertyName(NextItemListProperty);
                        serializer.Serialize(writer, nextItems);
                    }
                }
            }
            writer.WriteEndObject();
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.Null)
            return null;
        var jObject = JObject.Load(reader);

        // Detach and process $ref
        var refValue = (string)jObject[refProperty].RemoveFromLowestPossibleParent();
        if (refValue != null)
        {
            var reference = serializer.ReferenceResolver.ResolveReference(serializer, refValue);
            if (reference != null)
                return reference;
        }

        // Construct the value
        var contract = (JsonObjectContract)serializer.ContractResolver.ResolveContract(existingValue == null ? typeof(T) : existingValue.GetType());
        T value = (existingValue as T ?? (T)contract.DefaultCreator());

        // Detach and process $id
        var idValue = (string)jObject[idProperty].RemoveFromLowestPossibleParent();
        if (idValue != null)
        {
            serializer.ReferenceResolver.AddReference(serializer, idValue, value);
        }

        // Detach the (possibly large) list of next items.
        var nextItemList = jObject[NextItemListProperty].RemoveFromLowestPossibleParent();

        // populate the data properties (if any)
        serializer.Populate(jObject.CreateReader(), value);

        // Set the next item references
        if (nextItemList != null)
        {
            var list = nextItemList.ToObject>(serializer);
            SetNextItemLinks(value, list, contract);
        }

        return value;
    }
}

public struct PushValue : IDisposable
{
    Action setValue;
    T oldValue;

    public PushValue(T value, Func getValue, Action setValue)
    {
        if (getValue == null || setValue == null)
            throw new ArgumentNullException();
        this.setValue = setValue;
        this.oldValue = getValue();
        setValue(value);
    }

    #region IDisposable Members

    // By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
    public void Dispose()
    {
        if (setValue != null)
            setValue(oldValue);
    }

    #endregion
}

public static class JsonExtensions
{
    public static JToken RemoveFromLowestPossibleParent(this JToken node)
    {
        if (node == null)
            return null;
        var contained = node.AncestorsAndSelf().Where(t => t.Parent is JContainer && t.Parent.Type != JTokenType.Property).FirstOrDefault();
        if (contained != null)
            contained.Remove();
        // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should
        if (node.Parent is JProperty)
            ((JProperty)node.Parent).Value = null;
        return node;
    }
}

然后,给定稍微修改的类Chacha

class Chacha
{
    public Chacha NextChacha { get; set; }

    public long Data { get; set; }
}

为3个项目的数组生成以下JSON:

{
  "$type": "Question41828014.Chacha[], Tile",
  "$values": [
    {
      "$id": "1",
      "Data": 0,
      "nextItemList": {
        "$type": "System.Collections.Generic.List`1[[Question41828014.Chacha, Tile]], mscorlib",
        "$values": [
          {
            "$id": "2",
            "Data": 1
          },
          {
            "$id": "3",
            "Data": 2
          }
        ]
      }
    },
    {
      "$ref": "2"
    },
    {
      "$ref": "3"
    }
  ]
}

注意,JSON深度现在受到严格限制。小提琴的例子。

请注意,为类型指定自定义转换器后,它需要手动完成所有操作。如果您的类型Chacha是多态的,并且需要读取和写入"$type"属性,则需要自己将该逻辑添加到转换器中。

顺便说一句,我建议使用TypeNameHandling.Objects而不是TypeNameHandling.All。可以在JSON中合理地指定对象类型(只要正确清理了类型),但是应该在代码中指定集合类型。这样做可以从数组切换到,List而不必重新读取旧版JSON文件。

推荐阅读
凹凸曼00威威_694
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有