Ond*_*jko 1 c# stack-overflow serialization json.net
当我尝试序列化具有类似结构的对象时,我的C#程序正在运行StackOverflowException:
下面的示例代码:
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);
}
Run Code Online (Sandbox Code Playgroud)
JSON.NET版本是:9.0.1
.NET Framework:4.5.2有什么
解决方案如何序列化此结构?
欢迎任何帮助或建议。谢谢
出现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<Chacha>
{
protected override bool IsNextItemProperty(JsonProperty member)
{
return member.UnderlyingName == "NextChacha"; // Use nameof(Chacha.NextChacha) in latest c#
}
}
public abstract class LinkedListItemConverter<T> : 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<T> GetNextItemList(object value, JsonObjectContract contract)
{
var property = contract.Properties.Where(p => IsNextItemProperty(p)).Single();
List<T> list = null;
for (var item = (T)property.ValueProvider.GetValue(value); item != null; item = (T)property.ValueProvider.GetValue(item))
{
if (list == null)
list = new List<T>();
list.Add(item);
}
return list;
}
void SetNextItemLinks(object value, List<T> 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<int>(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<List<T>>(serializer);
SetNextItemLinks(value, list, contract);
}
return value;
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> 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;
}
}
Run Code Online (Sandbox Code Playgroud)
然后,给定稍微修改的类Chacha:
class Chacha
{
public Chacha NextChacha { get; set; }
public long Data { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
为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"
}
]
}
Run Code Online (Sandbox Code Playgroud)
注意,JSON深度现在受到严格限制。小提琴的例子。
请注意,为类型指定自定义转换器后,它需要手动完成所有操作。如果您的类型Chacha是多态的,并且需要读取和写入"$type"属性,则需要自己将该逻辑添加到转换器中。
顺便说一句,我建议使用TypeNameHandling.Objects而不是TypeNameHandling.All。可以在JSON中合理地指定对象类型(只要正确清理了类型),但是应该在代码中指定集合类型。这样做可以从数组切换到,List<T>而不必重新读取旧版JSON文件。
| 归档时间: |
|
| 查看次数: |
4623 次 |
| 最近记录: |