我将这些简单的代码写为Serialize类,以使其扁平化,但是当我使用[JsonConverter(typeof(FJson))]批注时,它将引发 Exception 。如果我SerializeObject手动拨打电话,它可以正常工作。
[JsonConverter(typeof(FJson))]
SerializeObject
如何在注释模式下使用JsonConvert:
class Program { static void Main(string[] args) { A a = new A(); a.id = 1; a.b.name = "value"; string json = null; // json = JsonConvert.SerializeObject(a, new FJson()); without [JsonConverter(typeof(FJson))] annotation workd fine // json = JsonConvert.SerializeObject(a); StackOverflowException Console.WriteLine(json); Console.ReadLine(); } } //[JsonConverter(typeof(FJson))] StackOverflowException public class A { public A() { this.b = new B(); } public int id { get; set; } public string name { get; set; } public B b { get; set; } } public class B { public string name { get; set; } } public class FJson : JsonConverter { public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { JToken t = JToken.FromObject(value); if (t.Type != JTokenType.Object) { t.WriteTo(writer); return; } JObject o = (JObject)t; writer.WriteStartObject(); WriteJson(writer, o); writer.WriteEndObject(); } private void WriteJson(JsonWriter writer, JObject value) { foreach (var p in value.Properties()) { if (p.Value is JObject) WriteJson(writer, (JObject)p.Value); else p.WriteTo(writer); } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override bool CanConvert(Type objectType) { return true; // works for any type } }
Json.NET对调用JToken.FromObject生成“默认”序列化然后修改结果JToken的输出的转换器不提供方便的支持- 正是因为您所观察StackOverflowException到的递归调用JsonConverter.WriteJson()会发生。
JToken.FromObject
JToken
StackOverflowException
JsonConverter.WriteJson()
一种解决方法 是使用线程静态布尔值在递归调用中临时禁用转换器。使用线程静态是因为在某些情况下,包括asp.net-web- api,JSON转换器的实例将在线程之间共享。在这种情况下,通过实例属性禁用转换器将不是线程安全的。
public class FJson : JsonConverter { [ThreadStatic] static bool disabled; // Disables the converter in a thread-safe manner. bool Disabled { get { return disabled; } set { disabled = value; } } public override bool CanWrite { get { return !Disabled; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { JToken t; using (new PushValue<bool>(true, () => Disabled, (canWrite) => Disabled = canWrite)) { t = JToken.FromObject(value, serializer); } if (t.Type != JTokenType.Object) { t.WriteTo(writer); return; } JObject o = (JObject)t; writer.WriteStartObject(); WriteJson(writer, o); writer.WriteEndObject(); } private void WriteJson(JsonWriter writer, JObject value) { foreach (var p in value.Properties()) { if (p.Value is JObject) WriteJson(writer, (JObject)p.Value); else p.WriteTo(writer); } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override bool CanConvert(Type objectType) { return true; // works for any type } } 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 }
完成此操作后,您可以将还原[JsonConverter(typeof(FJson))]到您的班级A:
A
[JsonConverter(typeof(FJson))] public class A { }
演示小提琴#1 在这里。
第二个更简单的变通办法 是为应用了的类型生成默认序列化,JsonConverter它利用了一个事实,即应用于 成员的 转换器会取代应用于 类型 或设置中的转换器。从文档:
JsonConverter
使用JsonConverter的优先级是由成员上的属性定义的JsonConverter,然后是由类中的属性定义的JsonConverter,最后是传递给JsonSerializer的所有转换器。
因此,可以通过将单个类型的成员嵌套在DTO内来为您的类型生成默认序列化,该成员的值是您的类型的实例,并且应用了虚拟转换器,除了转换为读取和读取的默认序列化之外,它什么都没做写作。
以下扩展方法和转换器可以完成此工作:
public static partial class JsonExtensions { public static JToken DefaultFromObject(this JsonSerializer serializer, object value) { if (value == null) return JValue.CreateNull(); var dto = Activator.CreateInstance(typeof(DefaultSerializationDTO<>).MakeGenericType(value.GetType()), value); var root = JObject.FromObject(dto, serializer); return root["Value"].RemoveFromLowestPossibleParent() ?? JValue.CreateNull(); } public static object DefaultToObject(this JToken token, Type type, JsonSerializer serializer = null) { var oldParent = token.Parent; var dtoToken = new JObject(new JProperty("Value", token)); var dtoType = typeof(DefaultSerializationDTO<>).MakeGenericType(type); var dto = (IHasValue)(serializer ?? JsonSerializer.CreateDefault()).Deserialize(dtoToken.CreateReader(), dtoType); if (oldParent == null) token.RemoveFromLowestPossibleParent(); return dto == null ? null : dto.GetValue(); } public static JToken RemoveFromLowestPossibleParent(this JToken node) { if (node == null) return null; // If the parent is a JProperty, remove that instead of the token itself. var contained = node.Parent is JProperty ? node.Parent : node; contained.Remove(); // Also detach the node from its immediate containing property -- Remove() does not do this even though it seems like it should if (contained is JProperty) ((JProperty)node.Parent).Value = null; return node; } interface IHasValue { object GetValue(); } [JsonObject(NamingStrategyType = typeof(DefaultNamingStrategy), IsReference = false)] class DefaultSerializationDTO<T> : IHasValue { public DefaultSerializationDTO(T value) { this.Value = value; } public DefaultSerializationDTO() { } [JsonConverter(typeof(NoConverter)), JsonProperty(ReferenceLoopHandling = ReferenceLoopHandling.Serialize)] public T Value { get; set; } object IHasValue.GetValue() { return Value; } } } public class NoConverter : JsonConverter { // NoConverter taken from this answer https://stackoverflow.com/a/39739105/3744182 // To https://stackoverflow.com/questions/39738714/selectively-use-default-json-converter // By https://stackoverflow.com/users/3744182/dbc public override bool CanConvert(Type objectType) { throw new NotImplementedException(); /* This converter should only be applied via attributes */ } public override bool CanRead { get { return false; } } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException(); } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
然后FJson.WriteJson()按以下方式使用它:
FJson.WriteJson()
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { JToken t = serializer.DefaultFromObject(value); // Remainder as before if (t.Type != JTokenType.Object) { t.WriteTo(writer); return; } JObject o = (JObject)t; writer.WriteStartObject(); WriteJson(writer, o); writer.WriteEndObject(); }
这种方法的优点是:
它不依赖于递归禁用转换器,因此可以与递归数据模型一起正常工作。
它不需要重新实现从对象属性序列化对象的整个逻辑。
演示小提琴#2 在这里。
笔记
两种转换器版本都只能处理写入。阅读未实现。
您编写的转换器会创建具有重复名称的JSON:
{ "id": 1, "name": null, "name": "value" }
尽管这不是严格非法的,但通常被认为是不良做法,因此应避免使用。