gpt4 book ai didi

c# - 自定义引用循环处理

转载 作者:太空宇宙 更新时间:2023-11-03 10:54:55 25 4
gpt4 key购买 nike

我正在尝试实现自定义引用循环处理。我需要做的就是写空对象代替嵌套对象。

预期结果

 { Id:1, Field:"Value", NestedObject:{Id:1}}


我创建了 JsonConverter

public class SerializationConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return true; } }


public override bool CanConvert(Type objectType)
{
return objectType == typeof(Form) || typeof(Form).IsAssignableFrom(objectType);
}

private HashSet<Form> serializedForms = new HashSet<Form>();

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
writer.WriteNull();

var f = (Form)value;
if (!serializedForms.Add(f))
writer.WriteRawValue("{Id:" + f.Id.Value + "}");
else
serializer.Serialize(writer, value);
}
}


但是,正如预期的那样,对 serializer.Serialize(writer, value)的内部调用的序列化器再次调用了我的转换器。

我仅在对象已被序列化时才尝试替换序列化结果,否则请使用默认序列化行为。

最佳答案

首先,我想提一下Json.Net具有内置的PreserveReferencesHandling设置,它可以自动为您处理这种事情,而无需特殊的转换器。将PreserveReferencesHandling设置为All时,Json.Net将内部引用ID分配给每个对象,并将特殊的$id$ref属性写入JSON以跟踪引用。对于您的示例,JSON输出如下所示:

{"$id":"1","Id":1,"Field":"Value","NestedObject":{"$ref":"1"}}


您会注意到这与问题的期望输出非常相似。这还有一个优点,即可以轻松地将其保留所有引用,反序列化回原始对象图,而无需再次执行任何特殊操作。

但是,现在让我们假设您有自己的理由要实现自定义引用循环处理,并查看代码为什么不起作用。

当Json.Net遇到对象的 JsonConverter时,它假定转换器将处理编写该对象所需的任何JSON。因此,如果要包括某些属性,则必须自己写出来。您可以使用序列化程序来帮助编写对象的某些部分,但是您不能仅将整个对象交给序列化程序并说“序列化”,因为这最终将导致回调到转换器中。

在大多数情况下,这样做将导致无限循环。对于您而言,它不是,因为您是在第一次调用 WriteJson时将表单添加到HashSet的。当序列化器第二次回调时,使用另一个分支,因为该表单已在集合中。因此,该对象的整个JSON最终为 {Id:1}而不是您真正想要的。

防止序列化程序回调到转换器中的一种方法是在转换器内部创建 JsonSerializer的新实例,并使用该实例代替传递到 WriteJson方法中的实例。新实例将不会引用您的转换器,因此您的 Form将被正常序列化。

不幸的是,这个想法也行不通:如果您没有在内部序列化器上引用转换器,那么Json.Net将无法知道如何对 NestedObject进行特殊的序列化处理!相反,它将被简单地省略,因为我们将不得不将 ReferenceLoopHandling设置为 Ignore以避免错误。如此看来,您有22个陷阱。

那么我们如何才能使它正常工作呢?好吧,让我们退后一步,在输出方面重新定义您真正想要发生的事情:


如果遇到已经见过的表单,我们只想输出 Id
否则,在表单中添加我们已经看到的表单列表,然后输出 IdFieldNestedObject


请注意,在两种情况下,我们都希望输出 Id,因此我们可以简化逻辑:


始终输出ID
如果遇到尚未看到的Form,请将表单添加到已看到的表单列表中,然后输出 FieldNestedObject


为了使事情变得简单,我们可以使用 JObject收集我们要输出的属性,然后将其最后简单地写入 writer

这是修改后的代码:

public class SerializationConverter : JsonConverter
{
public override bool CanRead { get { return false; } }
public override bool CanWrite { get { return true; } }

public override bool CanConvert(Type objectType)
{
return objectType == typeof(Form) || typeof(Form).IsAssignableFrom(objectType);
}

private HashSet<Form> serializedForms = new HashSet<Form>();

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Form f = (Form)value;

JObject jo = new JObject();
jo.Add("Id", f.Id);

if (serializedForms.Add(f))
{
jo.Add("Field", f.Field);
if (f.NestedObject != null)
{
jo.Add("NestedObject", JToken.FromObject(f.NestedObject, serializer));
}
}

jo.WriteTo(writer);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}


现在让我们测试一下:

class Program
{
static void Main(string[] args)
{
Form form = new Form
{
Id = 1,
Field = "Value",
};
form.NestedObject = form;

JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new SerializationConverter() },
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
};

string json = JsonConvert.SerializeObject(form, settings);
Console.WriteLine(json);
}
}

class Form
{
public int Id { get; set; }
public string Field { get; set; }
public Form NestedObject { get; set; }
}


这是输出:

{"Id":1,"Field":"Value","NestedObject":{"Id":1}}


到目前为止看起来不错。那么更严格的事情呢:

class Program
{
static void Main(string[] args)
{
List<Form> forms = new List<Form>
{
new Form
{
Id = 1,
Field = "One",
NestedObject = new Form
{
Id = 2,
Field = "Two"
}
},
new Form
{
Id = 3,
Field = "Three"
},
new Form
{
Id = 4,
Field = "Four"
},
new Form
{
Id = 5,
Field = "Five"
}
};

forms[0].NestedObject.NestedObject = forms[3];
forms[1].NestedObject = forms[0].NestedObject;
forms[2].NestedObject = forms[1];

JsonSerializerSettings settings = new JsonSerializerSettings
{
Converters = new List<JsonConverter> { new SerializationConverter() },
ReferenceLoopHandling = ReferenceLoopHandling.Serialize,
Formatting = Formatting.Indented
};

string json = JsonConvert.SerializeObject(forms, settings);
Console.WriteLine(json);
}
}


输出:

[
{
"Id": 1,
"Field": "One",
"NestedObject": {
"Id": 2,
"Field": "Two",
"NestedObject": {
"Id": 5,
"Field": "Five"
}
}
},
{
"Id": 3,
"Field": "Three",
"NestedObject": {
"Id": 2
}
},
{
"Id": 4,
"Field": "Four",
"NestedObject": {
"Id": 3
}
},
{
"Id": 5
}
]


编辑

如果 Form类具有大量字段,则可能需要使用反射,而不是单独列出转换器中的属性。这是使用反射的 WriteJson方法的外观:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Form f = (Form)value;

JObject jo = new JObject();
if (serializedForms.Add(f))
{
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (prop.CanRead)
{
object propVal = prop.GetValue(value);
if (propVal != null)
{
jo.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
}
}
else
{
jo.Add("Id", f.Id);
}

jo.WriteTo(writer);
}

关于c# - 自定义引用循环处理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19894217/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com