当我尝试序列化具有类似结构的对象时,我的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);
}
JSON.NET版本是:9.0.1
.NET Framework:4.5.2
如何序列化这种结构的任何解决方案?
欢迎任何帮助或建议。谢谢
答案 0 :(得分:5)
您获得stackoverflow异常的原因是Json.NET是一个递归的单遍树或图形序列化程序,在启用PreserveReferencesHandling.Objects
时,始终序列化第一个事件每个对象。您已构造了15,000个元素Chacha []
数组,以便第一个条目是包含按顺序链接的所有其他项的链接列表的头部。 Json.NET将尝试通过15,000级递归将序列化为嵌套的JSON对象15,000级,在此过程中溢出堆栈。
因此,您需要做的是仅在列表的 head 处编写整个链接表,作为JSON数组。不幸的是,Json.NET也是一个基于契约的序列化器,这意味着无论嵌套深度是什么,它都会在遇到给定类型的对象时尝试编写相同的属性。因此,向Chacha[] NextChachaList
对象添加Chacha
属性无济于事,因为它将在每个级别编写。相反,有必要创建一个相当复杂的custom 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;
}
}
然后,给出略微修改的类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深度现在受到严格限制。示例fiddle。
请注意,一旦为类型指定了自定义转换器,它就需要手动完成所有操作。如果您的类型Chacha
是多态的,并且您需要读取和写入"$type"
属性,则需要自己将该逻辑添加到转换器。
顺便说一句,我建议TypeNameHandling.Objects
而不是TypeNameHandling.All
。可以在JSON中合理地指定对象类型(只要类型正确sanitized),但应在代码中指定集合类型。这样做可以从数组切换到List<T>
,而无需发布旧版JSON文件。
答案 1 :(得分:1)
MVC 与 Jquery:
通过控制台nuget包安装newtonsoft包: 安装包 Newtonsoft.json -版本 6.0.1
控制器:
[HttpPost]
public ActionResult Get_Country()
{
string _data = "";
SqlCommand cmd = new SqlCommand("Get_Country", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return Json(_data, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult Insert(string A,string B,string C,string D)
{
con.Open();
SqlCommand cmd = new SqlCommand("Insert_Employee", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Name", A);
cmd.Parameters.AddWithValue("@CID", B);
cmd.Parameters.AddWithValue("@Gender", C);
cmd.Parameters.AddWithValue("@Hobbies", D);
int count = cmd.ExecuteNonQuery();
con.Close();
return View();
}
[HttpPost]
public ActionResult Get_Employees()
{
string _data = "";
SqlCommand cmd = new SqlCommand("Get_Employees", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return Json(_data, JsonRequestBehavior.AllowGet);
}
[HttpPost]
public ActionResult EditData(int A)
{
string _data = "";
SqlCommand cmd = new SqlCommand("Get_Employees_Edit", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@ID", A);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return Json(_data, JsonRequestBehavior.AllowGet);
}
查看:
脚本:
<script type="text/javascript">
var IDD = "";
$(document).ready(function () {
Get_Country();
GetEmployees();
});
function Get_Country() {
$.ajax({
url: 'Employee/Get_Country',
type: 'post',
data: {},
success: function (_dt) {
_dt = JSON.parse(_dt);
for (var i = 0; i < _dt.length; i++) {
$('#ddlCountry').append($('<option/>').attr('value',_dt[i].CID).text(_dt[i].CName));
}
},
error: function () {
alert('Error in Country Bind');
}
})
}
function SaveData() {
debugger
var HOB = "";
if ($("#btnsave").val() == ("Save")) {
HOB = $('input:checkbox:checked.B').map(function () {
return this.value;
}).get().join(',');
$.ajax({
url: 'Employee/Insert',
type: 'post',
data: { A: $('#txtName').val(), B: $('#ddlCountry').val(), C: $('input:radio[name=A]:checked').val(), D: HOB },
success: function () {
alert('data saved');
GetEmployees();
},
error: function () {
alert('saved error');
}
});
}
}
function GetEmployees() {
debugger
$.ajax({
url: 'Employee/Get_Employees',
type: 'post',
data: {},
async: false,
success: function (_dt) {
_dt = JSON.parse(_dt);
$('#tbl').find("tr:gt(0)").remove();
for (var i = 0; i < _dt.length; i++) {
$('#tbl').append('<tr><td>' + _dt[i].ID + '</td><td>' + _dt[i].Name + '</td><td>' + _dt[i].CName + '</td><td>' + (_dt[i].Gender == 1 ? 'Male' : _dt[i].Gender == 2 ? 'Female' : 'Others') + '</td><td>' + _dt[i].Hobbies + '</td><td><input id="btnEdit" type="button" value="Edit" onclick="EditData(' + _dt[i].ID + ')"/></td><td><input id="btnDelete" type="button" value="Delete" onclick="DeleteData(' + _dt[i].ID + ')"/></td></tr>')
}
},
error: function () {
alert('error in binding');
}
});
}
function EditData(id) {
debugger;
$.ajax({
url: 'Employee/EditData',
type: 'post',
data: { A: id },
success: function (_dt) {
_dt = JSON.parse(_dt);
debugger;
$("#txtName").val(_dt[0].Name);
$("#ddlCountry").val(_dt[0].CID);
$("input[name=A][value=" + _dt[0].Gender + "]").prop('checked', true);
var hbb = _dt[0].Hobbies;
var arr = hbb.split(',');
$('input:checkbox:checked.B').prop('checked', false);
for (var i = 0; i < arr.length; i++) {
$('input:checkbox[class=B][value=' + arr[i] + ']').prop('checked', true);
}
$("#btnsave").val("Update");
IDD = id;
},
error: function () {
alert('edit error !!');
}
});
}
身体:
<table>
<tr>
<td>Name :</td>
<td>
<input id="txtName" type="text" />
</td>
</tr>
<tr>
<td>Country :</td>
<td>
<select id="ddlCountry">
<option value="0"><--Select--></option>
</select>
</td>
</tr>
<tr>
<td>Gender :</td>
<td>
<input type="radio" name="A" value="1" />Male
<input type="radio" name="A" value="2" />Female
<input type="radio" name="A" value="3" />Others
</td>
</tr>
<tr>
<td>Hobbies :</td>
<td>
<input type="checkbox" class="B" value="Cricket" />Cricket
<input type="checkbox" class="B" value="Coding" />Coding
<input type="checkbox" class="B" value="Travelling" />Travelling
<input type="checkbox" class="B" value="Swimming" />Swimming
<input type="checkbox" class="B" value="Movies" />Movies
<input type="checkbox" class="B" value="Typing" />Typing
</td>
</tr>
<tr>
<td></td>
<td>
<input type="button" id="btnsave" value="Save" onclick="SaveData()" />
</td>
</tr>
</table>
<table id="tbl" border="1">
<tr>
<td>ID</td>
<td>Name</td>
<td>Country</td>
<td>Gender</td>
<td>Hobbies</td>
<td>Edit</td>
<td>Delete</td>
</tr>
</table>
答案 2 :(得分:0)
public class Model
{
public int Id { get; set; }
public string Name { get; set; }
public string SurName { get; set; }
}
List<Model> list = new List<Model>();
list.Add(new Model { Id = 1, Name = "Jon", SurName = "Snow"});
var stringJson = JsonConvert.SerializeObject(list, new JsonSerializerSettings
{
PreserveReferencesHandling = PreserveReferencesHandling.Objects
});
答案 3 :(得分:0)
Jquery cs code:
```csharp
SqlConnection con = new SqlConnection(ConfigurationManager.ConnectionStrings["DBCS"].ConnectionString);
[WebMethod]
public string GetCountry()
{
string _data = "";
con.Open();
SqlCommand cmd = new SqlCommand("usp_country_get",con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
con.Close();
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return _data;
}
[WebMethod]
public void Insert(string A, int B, int C, string D)
{
con.Open();
SqlCommand cmd = new SqlCommand("usp_emp_insert", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@name", A);
cmd.Parameters.AddWithValue("@cid", B);
cmd.Parameters.AddWithValue("@gender", C);
cmd.Parameters.AddWithValue("@hobbies", D);
cmd.ExecuteNonQuery();
con.Close();
}
[WebMethod]
public void Delete(int ID)
{
con.Open();
SqlCommand cmd = new SqlCommand("usp_emp_delete", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@id", ID);
cmd.ExecuteNonQuery();
con.Close();
}
[WebMethod]
public string Edit(int ID)
{
string _data = "";
con.Open();
SqlCommand cmd = new SqlCommand("usp_emp_edit", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@id", ID);
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
con.Close();
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return _data;
}
[WebMethod]
public void Update(int ID, string A, int B, int C, string D)
{
con.Open();
SqlCommand cmd = new SqlCommand("usp_emp_update", con);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@id", ID);
cmd.Parameters.AddWithValue("@name", A);
cmd.Parameters.AddWithValue("@cid", B);
cmd.Parameters.AddWithValue("@gender", C);
cmd.Parameters.AddWithValue("@hobbies", D);
cmd.ExecuteNonQuery();
con.Close();
}
[WebMethod]
public string GetEmployee()
{
string _data = "";
con.Open();
SqlCommand cmd = new SqlCommand("usp_emp_get", con);
cmd.CommandType = CommandType.StoredProcedure;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
con.Close();
if (ds.Tables[0].Rows.Count > 0)
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
else
{
_data = JsonConvert.SerializeObject(ds.Tables[0]);
}
return _data;
}
```
aspx code:
Scripting :
```html
<script src="jquery-3.2.1.js"></script>
<script type="text/javascript">
var IDD = "";
$(document).ready(function () {
CountryBind();
//EmployeeBind();
});
function CountryBind() {
$.ajax({
url:'Employee.asmx/GetCountry',
type:'post',
contentType:'application/json;charset=utf-8',
dataType:'json',
data:'{}',
async: false,
success: function (_dt) {
_dt = JSON.parse(_dt.d);
for (var i = 0; i < _dt.length; i++) {
$("#ddlcountry").append($('<option/>').attr("value", _dt[i].cid).text(_dt[i].cname));
}
},
error: function () {
alert('CountryBind error');
}
});
}
function SaveData() {
var HOB = "";
HOB = $('input:checkbox:checked.B').map(function () {
return this.value;
}).get().join(',');
if ($("#btnsave").val()==("Save")) {
$.ajax({
url: 'Employee.asmx/Insert',
type: 'post',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: "{A:'" + $("#txtname").val() + "',B:'" + $("#ddlcountry").val() + "',C:'" + $('input:radio[name=A]:checked').val() + "',D:'" + HOB + "'}",
success: function () {
alert('insert success !!');
EmployeeBind();
},
error: function () {
alert('insert error !!');
}
});
}
else {
$.ajax({
url: 'Employee.asmx/Update',
type: 'post',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: "{ID:" + IDD + ",A:'" + $("#txtname").val() + "',B:'" + $("#ddlcountry").val() + "',C:'" + $('input:radio[name=A]:checked').val() + "',D:'" + HOB + "'}",
success: function () {
alert('Update Successfully');
EmployeeBind();
},
error: function () {
alert('Update error');
}
});
}
}
function EmployeeBind() {
$.ajax({
url: 'Employee.asmx/GetEmployee',
type: 'post',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: "{}",
async: false,
success: function (_dt) {
_dt = JSON.parse(_dt.d);
$("#tbl").find("tr:gt(0)").remove();
for (var i = 0; i < _dt.length; i++) {
$("#tbl").append('<tr><td>' + _dt[i].name + '</td><td>' + _dt[i].cname + '</td><td>' + (_dt[i].gender == "1" ? "Male" : _dt[i].gender == "2" ? "FeMale" : "Others") + '</td><td>' + _dt[i].hobbies + '</td><td><input type="button" id="btnedit" value="Edit" onclick="EditData(' + _dt[i].id + ')" /><td><input type="button" id="btndelete" value="Delete" onclick="DeleteData(' + _dt[i].id + ')" /></td</tr>');
}
},
error: function () {
alert('EmployeeBind error');
}
});
}
function DeleteData(id) {
$.ajax({
url: 'Employee.asmx/Delete',
type: 'post',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
data: "{ID:'" + id + "'}",
success: function () {
alert('Delete Successfully');
EmployeeBind();
},
error: function () {
alert('DeleteData error');
}
});
}
function EditData(id) {
$.ajax({
url: 'Employee.asmx/Edit',
type: 'post',
contentType: 'application/json;charset=utf-8',
dataType: 'json',
async:false,
data: "{ID:'" + id + "'}",
success: function (_dt) {
_dt = JSON.parse(_dt.d);
$("#txtname").val(_dt[0].name);
$("#ddlcountry").val(_dt[0].cid);
$("input[name=A][value=" + _dt[0].gender + "]").prop('checked', true);
var hbb = _dt[0].hobbies;
var arr = hbb.split(',');
$("input:checkbox:checked.B").prop('checked', false);
for (var i = 0; i < arr.length; i++) {
$("input:checkbox[class=B][value=" + arr[i] + ']').prop('checked', true);
}
$("#btnsave").val("Update");
IDD = id;
EmployeeBind();
},
error: function () {
alert('EditData error');
}
});
}
</script>
```
Body:
```html
<body>
<table>
<tr>
<td>Name :</td>
<td><input type="text" id="txtname" /></td>
</tr>
<tr>
<td>Country :</td>
<td><select id="ddlcountry">
<option value="0">--Select--</option>
</select>
</td>
</tr>
<tr>
<td>Gender :</td>
<td>
<input type="radio" name="A" value="1" /> male
<input type="radio" name="A" value="2" /> female
<input type="radio" name="A" value="3" /> other
</td>
</tr>
<tr>
<td>Hobbies :</td>
<td>
<input type="checkbox" class="B" value="cricket" /> cricket
<input type="checkbox" class="B" value="music" /> music
<input type="checkbox" class="B" value="movies" /> movies
<input type="checkbox" class="B" value="cooking" /> cooking
<input type="checkbox" class="B" value="coding" /> coding
</td>
</tr>
<tr>
<td></td>
<td><input type="button" id="btnsave" value="Save" onclick="SaveData()" /></td>
</tr>
</table>
<table id="tbl" border="1" style="border-color:darkblue;width:100%">
<tr>
<th>Name</th>
<th>Country</th>
<th>Gender</th>
<th>Hobbies</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</table>
</body>
```