5

I have a list of objects (A), each object containing a list of objects (B). I did the serialization of the list of As without problems but when I did the deserialization of As the list of Bs inside of each A, has twice the original quantity of Bs. Why is this happening?

        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;
    }
}
Fidel Garcia
  • 405
  • 1
  • 6
  • 16
  • 1
    It may be the fact that C# Compiler adds Properties as back hand fields under the hood. – Orel Eraki Feb 16 '16 at 23:03
  • I'm not that familiar with NewtonSoft, but I use it to serialize/deserialize classes, but why have you got a ContractResolver class? – Thierry Feb 16 '16 at 23:04

3 Answers3

4

It happens because C# Compiler generate a backing fields under the hood for Properties.

You can either remove the custom created Resolver and let Json.NET do its magic, or use the small hack at the end.

Auto-Implemented Properties

Automatically implemented (auto-implemented) properties automate this pattern. More specifically, non-abstract property declarations are allowed to have semicolon accessor bodies. Both accessors must be present and both must have semicolon bodies, but they can have different accessibility modifiers. When a property is specified like this, a backing field will automatically be generated for the property, and the accessors will be implemented to read from and write to that backing field. The name of the backing field is compiler generated and inaccessible to the user.

You can make achieve what you're looking for by using a small hack, although i would suggest you otherwise.
Additionally, I would rethink if you indeed need BindingFlags.NonPublic because removing it alone, will solve your issue.

Small hack

type
    .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
    .Where(field => !field.Name.EndsWith("k__BackingField"))
    .Select(f => base.CreateProperty(f, memberSerialization))
)
Orel Eraki
  • 11,940
  • 3
  • 28
  • 36
  • As a matter of interest, why would you want to go down this route? I've just added the resolver to my project to see what it actually did, and it's making the serialize data a lot more complex (and larger) than it needs to be, but there must be a reason for wanting to use this? – Thierry Feb 16 '16 at 23:56
  • I also suspect that is due to a problem with serializing the backing fields. What is strange is that it only happens with the nested list, the principal list (As) does not change the quantity even though I'm serializing and deserializing the backing fields, what makes me wonder if the problem is my ContractResolver class or is a kind of bug of Newtonsoft when it deserialize the nested list. I need the ContractResolver just like I published it, the real environment is more complex than As and Bs. – Fidel Garcia Feb 17 '16 at 15:29
  • The solution of not serialize the backing fields I know that works but is a hack and I'm trying to avoid it always I can and I still don't know the reason of the problem. – Fidel Garcia Feb 17 '16 at 15:30
  • @FidelGarcia *I still don't know the reason of the problem* - Auto properties in C# have hidden compiler-generated backing fields. Your contract resolver exposes these fields so that Json.Net serializes them *in addition to* the properties. So you end up with two copies of the information in the JSON. When you deserialize, both copies get processed. Since the list property and the backing field point to the same list, and Json.Net's default behavior is to reuse existing lists, you end up with duplicate items. Best not to include the backing fields if including the properties. – Brian Rogers Oct 11 '19 at 21:55
2

I solved this by setting the ObjectCreationHandling to Replace

From Newtonsoft documentation:

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

Model:

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

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

Sample code:

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

I don't really understand why they would be duplicated using the default settings or why you would ever want that, however, but their example is exactly what was happening to me.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
LarryBud
  • 990
  • 9
  • 20
  • 1
    See [Repeated serialization and deserialization creates duplicate items](https://stackoverflow.com/q/24835262/10263) for an explanation as to why this happens. – Brian Rogers Oct 11 '19 at 15:16
  • Note that your issue is a little bit different than the OP's. His issue was caused by the use of a custom contract resolver which was exposing the compiler-generated backing fields on his properties (including the list property). Yours is caused by initializing the list in the constructor. – Brian Rogers Oct 11 '19 at 15:41
1

As mentioned in my question, I'm not sure why you have the ContractResolver, but when I use the following:

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

The data is serialized and deserialized as expected.

Thierry
  • 6,142
  • 13
  • 66
  • 117