【问题标题】:Newtonsoft deserialization duplicate elements when deserialize a list inside other list反序列化其他列表中的列表时,Newtonsoft 反序列化重复元素
【发布时间】:2016-02-16 22:58:43
【问题描述】:

我有一个对象列表 (A),每个对象都包含一个对象列表 (B)。我没有问题地对As列表进行了序列化,但是 当我对每个 A 内部的 B 列表进行反序列化时,B 的原始数量是原来的两倍。为什么会这样?

        var sample = new List<A>
        {
            new A
            {
                Flag = true,
                Amount = 10,
                Bs = new List<B>
                {
                    new B {Amount = 4, Id = Guid.NewGuid()},
                    new B {Amount = 6, Id = Guid.NewGuid()}
                }
            },
            new A
            {
                Flag = true,
                Amount = 20,
                Bs = new List<B>
                {
                    new B {Amount = 4, Id = Guid.NewGuid()},
                    new B {Amount = 6, Id = Guid.NewGuid()}
                }
            },
            new A
            {
                Flag = false,
                Amount = 30,
                Bs = new List<B>
                {
                    new B {Amount = 4, Id = Guid.NewGuid()},
                    new B {Amount = 6, Id = Guid.NewGuid()}
                }
            }
        };

        var serialized = JsonConvert.SerializeObject(sample, ContractResolver.AllMembersSettings);
        var deserialized = JsonConvert.DeserializeObject<List<A>>(serialized, ContractResolver.AllMembersSettings);

class A
{
    public bool Flag { get; set; }
    public decimal Amount { get; set; }
    public List<B> Bs { get; set; }
}

class B
{
    public Guid Id { get; set; }
    public decimal Amount { get; set; }
}

public class ContractResolver : DefaultContractResolver
{
    public static readonly JsonSerializerSettings AllMembersSettings =
        new JsonSerializerSettings
        {
            TypeNameHandling = TypeNameHandling.All,
            ContractResolver = new ContractResolver()
        };

    protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
    {
        var props =
            type
                .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(p => p.CanRead && p.CanWrite)
                .Select(p => base.CreateProperty(p, memberSerialization))
            .Union(
            type
                .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Select(f => base.CreateProperty(f, memberSerialization)))
            .ToList();

        props.ForEach(p => { p.Writable = true; p.Readable = true; });

        return props;
    }
}

【问题讨论】:

  • 这可能是因为 C# 编译器在后台添加了属性作为后台字段。
  • 我对 NewtonSoft 不是很熟悉,但是我用它来序列化/反序列化类,但是为什么你有一个 ContractResolver 类?

标签: c# json.net deserialization


【解决方案1】:

这是因为 C# 编译器在后台为属性生成了一个支持字段。

您可以删除自定义创建的解析器并让 Json.NET 发挥作用,或者使用最后的小技巧。

Auto-Implemented Properties

自动实现(auto-implemented)的属性自动执行此操作 图案。更具体地说,非抽象属性声明是 允许有分号访问器主体。两个访问器都必须是 present 并且两者都必须有分号正文,但它们可以有 不同的可访问性修饰符。当一个属性被指定为 这样,将自动为 属性,并且访问器将被实现为读取和写入 到那个支持领域。支持字段的名称是编译器 生成且用户无法访问。

您可以通过使用一个小技巧来实现您正在寻找的东西,尽管我建议您不这样做。
此外,如果您确实需要 BindingFlags.NonPublic,我会重新考虑,因为单独删除它会解决您的问题。

小技巧

type
    .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
    .Where(field => !field.Name.EndsWith("k__BackingField"))
    .Select(f => base.CreateProperty(f, memberSerialization))
)

【讨论】:

  • 出于兴趣,您为什么要走这条路?我刚刚将解析器添加到我的项目中以查看它实际做了什么,它使序列化数据比它需要的更复杂(和更大),但一定有理由想要使用它?
  • 我还怀疑这是由于序列化支持字段的问题。奇怪的是它只发生在嵌套列表中,即使我正在序列化和反序列化支持字段,主体列表(As)也不会改变数量,这让我想知道问题是我的 ContractResolver 类还是Newtonsoft 反序列化嵌套列表时的一种错误。我需要ContractResolver,就像我发布的一样,真实环境比As和Bs更复杂。
  • 不序列化支持字段的解决方案我知道这是可行的,但这是一个 hack,我总是尽量避免它,但我仍然不知道问题的原因。跨度>
  • @FidelGarcia 我仍然不知道问题的原因 - C# 中的自动属性隐藏了编译器生成的支持字段。您的合约解析器会公开这些字段,以便 Json.Net 序列化它们除了属性。因此,您最终会得到 JSON 中信息的两个副本。当您反序列化时,两个副本都会得到处理。由于列表属性和支持字段指向同一个列表,并且 Json.Net 的默认行为是重用现有列表,因此您最终会得到重复的项目。如果包含属性,最好不要包含支持字段。
【解决方案2】:

正如我的问题中提到的,我不确定您为什么拥有 ContractResolver,但是当我使用以下内容时:

string sampleData = Newtonsoft.Json.JsonConvert.SerializeObject(sample);
List<A> test = Newtonsoft.Json.JsonConvert.DeserializeObject<List<A>>(sampleData);

数据按预期序列化和反序列化。

【讨论】:

  • 我使用 ContractResolver 类来避免在我的类上放置属性
【解决方案3】:

我通过将ObjectCreationHandling 设置为Replace 解决了这个问题

来自 Newtonsoft 文档:

https://www.newtonsoft.com/json/help/html/DeserializeObjectCreationHandling.htm

型号:

public class UserViewModel
{
    public string Name { get; set; }
    public IList<string> Offices { get; private set; }

    public UserViewModel()
    {
        Offices = new List<string>
        {
            "Auckland",
            "Wellington",
            "Christchurch"
        };
    }
}

示例代码:

string json = @"{
  'Name': 'James',
  'Offices': [
    'Auckland',
    'Wellington',
    'Christchurch'
  ]
}";

UserViewModel model1 = JsonConvert.DeserializeObject<UserViewModel>(json);

foreach (string office in model1.Offices)
{
    Console.WriteLine(office);
}
// Auckland
// Wellington
// Christchurch
// Auckland
// Wellington
// Christchurch

UserViewModel model2 = JsonConvert.DeserializeObject<UserViewModel>(json, new JsonSerializerSettings
{
    ObjectCreationHandling = ObjectCreationHandling.Replace
});

foreach (string office in model2.Offices)
{
    Console.WriteLine(office);
}

// Auckland
// Wellington
// Christchurch

我真的不明白为什么会使用默认设置复制它们,或者为什么你会想要这样,但是他们的例子正是发生在我身上的事情。

【讨论】:

  • 请参阅 Repeated serialization and deserialization creates duplicate items 了解为什么会发生这种情况。
  • 请注意,您的问题与 OP 的问题略有不同。他的问题是由使用自定义合约解析器引起的,该解析器在他的属性(包括列表属性)上暴露了编译器生成的支持字段。你的是由在构造函数中初始化列表引起的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-06-24
  • 2013-06-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多