gpt4 book ai didi

c# - Json.NET,如何自定义序列化以插入 JSON 属性

转载 作者:IT王子 更新时间:2023-10-29 04:43:21 24 4
gpt4 key购买 nike

我一直无法为 JsonConvert.WriteJson 找到一个合理的实现,它允许我在序列化特定类型时插入 JSON 属性。我所有的尝试都导致了“JsonSerializationException:检测到类型为 XXX 的自引用循环”。

关于我试图解决的问题的更多背景知识:我使用 JSON 作为配置文件格式,并且使用 JsonConverter 来控制类型解析、序列化和我的配置类型的反序列化。我不想使用 $type 属性,而是想使用更有意义的 JSON 值来解析正确的类型。

在我简化的示例中,这里有一些 JSON 文本:

{
"Target": "B",
"Id": "foo"
}

其中 JSON 属性 "Target": "B" 用于确定此对象应序列化为类型 B。考虑到简单的示例,这种设计可能看起来并不那么引人注目,但它确实使配置文件格式更有用。

我还希望配置文件是可往返的。我有反序列化案例,但我无法工作的是序列化案例。

我的问题的根源是我找不到使用标准 JSON 序列化逻辑且不抛出“自引用循环”异常的 JsonConverter.WriteJson 的实现。这是我的实现:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

//BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
// Same error occurs whether I use the serializer parameter or a separate serializer.
JObject jo = JObject.FromObject(value, serializer);
if (typeHintProperty != null)
{
jo.AddFirst(typeHintProperty);
}
writer.WriteToken(jo.CreateReader());
}

在我看来,这是 Json.NET 中的一个错误,因为应该有一种方法可以做到这一点。不幸的是,我遇到的所有 JsonConverter.WriteJson 示例(例如 Custom conversion of specific objects in JSON.NET)仅提供特定类的自定义序列化,使用 JsonWriter 方法写出单个对象和属性。

这是显示我的问题的 xunit 测试的完整代码(或 see it here)

using System;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Serialization;

using Xunit;


public class A
{
public string Id { get; set; }
public A Child { get; set; }
}

public class B : A {}

public class C : A {}

/// <summary>
/// Shows the problem I'm having serializing classes with Json.
/// </summary>
public sealed class JsonTypeConverterProblem
{
[Fact]
public void ShowSerializationBug()
{
A a = new B()
{
Id = "foo",
Child = new C() { Id = "bar" }
};

JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
string json = JsonConvert.SerializeObject(a, Formatting.Indented, jsonSettings);
Console.WriteLine(json);

Assert.Contains(@"""Target"": ""B""", json);
Assert.Contains(@"""Is"": ""C""", json);
}

[Fact]
public void DeserializationWorks()
{
string json =
@"{
""Target"": ""B"",
""Id"": ""foo"",
""Child"": {
""Is"": ""C"",
""Id"": ""bar"",
}
}";

JsonSerializerSettings jsonSettings = new JsonSerializerSettings();
jsonSettings.ContractResolver = new TypeHintContractResolver();
A a = JsonConvert.DeserializeObject<A>(json, jsonSettings);

Assert.IsType<B>(a);
Assert.IsType<C>(a.Child);
}
}

public class TypeHintContractResolver : DefaultContractResolver
{
public override JsonContract ResolveContract(Type type)
{
JsonContract contract = base.ResolveContract(type);
if ((contract is JsonObjectContract)
&& ((type == typeof(A)) || (type == typeof(B))) ) // In the real implementation, this is checking against a registry of types
{
contract.Converter = new TypeHintJsonConverter(type);
}
return contract;
}
}


public class TypeHintJsonConverter : JsonConverter
{
private readonly Type _declaredType;

public TypeHintJsonConverter(Type declaredType)
{
_declaredType = declaredType;
}

public override bool CanConvert(Type objectType)
{
return objectType == _declaredType;
}

// The real implementation of the next 2 methods uses reflection on concrete types to determine the declaredType hint.
// TypeFromTypeHint and TypeHintPropertyForType are the inverse of each other.

private Type TypeFromTypeHint(JObject jo)
{
if (new JValue("B").Equals(jo["Target"]))
{
return typeof(B);
}
else if (new JValue("A").Equals(jo["Hint"]))
{
return typeof(A);
}
else if (new JValue("C").Equals(jo["Is"]))
{
return typeof(C);
}
else
{
throw new ArgumentException("Type not recognized from JSON");
}
}

private JProperty TypeHintPropertyForType(Type type)
{
if (type == typeof(A))
{
return new JProperty("Hint", "A");
}
else if (type == typeof(B))
{
return new JProperty("Target", "B");
}
else if (type == typeof(C))
{
return new JProperty("Is", "C");
}
else
{
return null;
}
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (! CanConvert(objectType))
{
throw new InvalidOperationException("Can't convert declaredType " + objectType + "; expected " + _declaredType);
}

// Load JObject from stream. Turns out we're also called for null arrays of our objects,
// so handle a null by returning one.
var jToken = JToken.Load(reader);
if (jToken.Type == JTokenType.Null)
return null;
if (jToken.Type != JTokenType.Object)
{
throw new InvalidOperationException("Json: expected " + _declaredType + "; got " + jToken.Type);
}
JObject jObject = (JObject) jToken;

// Select the declaredType based on TypeHint
Type deserializingType = TypeFromTypeHint(jObject);

var target = Activator.CreateInstance(deserializingType);
serializer.Populate(jObject.CreateReader(), target);
return target;
}

public override bool CanWrite
{
get { return true; }
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

//BUG: JsonSerializationException : Self referencing loop detected with type 'B'. Path ''.
// Same error occurs whether I use the serializer parameter or a separate serializer.
JObject jo = JObject.FromObject(value, serializer);
if (typeHintProperty != null)
{
jo.AddFirst(typeHintProperty);
}
writer.WriteToken(jo.CreateReader());
}

}

最佳答案

如您所见,从转换器内部对正在转换的同一个对象调用 JObject.FromObject() 将导致递归循环。通常,解决方案是 (a) 在转换器内使用单独的 JsonSerializer 实例,或者 (b) 手动序列化属性,正如 James 在他的回答中指出的那样。你的情况有点特殊,因为这些解决方案都不适合你:如果你使用一个不知道转换器的单独的序列化程序实例,那么你的子对象将不会应用它们的提示属性。正如您在评论中提到的,完全手动序列化不适用于通用解决方案。

幸运的是,有一个中间立场。您可以在 WriteJson 方法中使用一些反射来获取对象属性,然后从那里委托(delegate)给 JToken.FromObject()。转换器将被递归调用,因为它应该为子属性调用,但不会为当前对象调用,因此您不会遇到麻烦。此解决方案的一个警告:如果您碰巧将任何 [JsonProperty] 属性应用于此转换器处理的类(在您的示例中为 A、B 和 C),这些属性将不会受到尊重。

这是 WriteJson 方法的更新代码:

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JProperty typeHintProperty = TypeHintPropertyForType(value.GetType());

JObject jo = new JObject();
if (typeHintProperty != null)
{
jo.Add(typeHintProperty);
}
foreach (PropertyInfo prop in value.GetType().GetProperties())
{
if (prop.CanRead)
{
object propValue = prop.GetValue(value);
if (propValue != null)
{
jo.Add(prop.Name, JToken.FromObject(propValue, serializer));
}
}
}
jo.WriteTo(writer);
}

fiddle :https://dotnetfiddle.net/jQrxb8

关于c# - Json.NET,如何自定义序列化以插入 JSON 属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26129448/

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