1

Currently I'm trying to utilize Json.Net serializer in my projects.

One of the problems that I've faced recentry is that deserialized object gets all of it's private list items duplicated. It can be reproduced like this:

  1. Create class with private field of List<> type, where List<> generic parameter is some other class
  2. Add public property, that gets/sets private field
  3. Create instance of that class, add 1 item to inner list, using property.
  4. Serialize with new DefaultContractResolver that is setup to see private class data;
  5. Deserialize

Code to reproduce the problem:

    public class ClassParam
    {
        public int? ParamOne
        {
            get;
            set;
        }
    }
    public class ClassWithParams
    {
        private List<ClassParam> _privateFieid = new List<ClassParam>();

        public List<ClassParam> PropertWithBackingField
        {
            get { return _privateFieid; }
            set { _privateFieid = value; }
        }
        
        public void AddElementToPrivateField(ClassParam classParam)
        {
            _privateFieid.Add(classParam);
        }
        
    }
    [Test]
    public void Test()
    {
            var instance = new ClassWithParams();

        var param1 = new ClassParam { ParamOne = 1 };

        instance.PropertWithBackingField.Add(param1);

        var contractResolver = new DefaultContractResolver();
        contractResolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;

        string serializedInstance = JsonConvert.SerializeObject(instance,
                                                                   Formatting.Indented,
                                                                   new JsonSerializerSettings()
                                                                   {
                                                                       ContractResolver = contractResolver
                                                                      
                                                                   });

        var deserializeInstance = JsonConvert.DeserializeObject(serializedInstance, typeof(ClassWithParams),
                                                                         new JsonSerializerSettings()
                                                                         {
                                                                             ContractResolver = contractResolver
                                                                         });
    }

1

When I remove public property PropertWithBackingField from ClassWithParams it's all ok. The problem is gone as well when I don't use custom setup for ContractResolver. But I do need to serialize private data of my classes as soon as not all of it is exposed via public properties.

What's wrong with my code? Are there any subtleties, using Json.Net or is it a bug?

Community
  • 1
  • 1
Konstantin Chernov
  • 1,899
  • 2
  • 21
  • 37

2 Answers2

1

For this code

var s = JsonConvert.SerializeObject(instance);
var desInst = JsonConvert.DeserializeObject<ClassWithParams>(s);

Your json will be {"PropertWithBackingField":[{"ParamOne":1}]}

But you say to include private field in serialization/deserialization with

contractResolver.DefaultMembersSearchFlags |= BindingFlags.NonPublic;

and get a json

   {
  "_privateFieid": [
    {
      "ParamOne": 1
    }
  ],
  "PropertWithBackingField": [
    {
      "ParamOne": 1
    }
  ]
 }

So there are two deserializations, one for _privateFieid and one for PropertWithBackingField

Either use BindingFlags.Public or use my code above with is much simpler.

L.B
  • 114,136
  • 19
  • 178
  • 224
  • What if I have one more private field of same type `List`, using your code I wouldn't serialize/deserialize it at all. As I've mentioned in the question - **I do need to serialize private data of my classes as soon as not all of it is exposed via public properties** – Konstantin Chernov Sep 27 '12 at 07:59
  • @RogerTaylor **Do not serialize** private fields which are exposed by public properties. Additionally, You can control which field to include in final json string by `JsonProperty`and `JsonIgnore` attributes, instead of instructing as a rule `BindingFlags.NonPublic` – L.B Sep 27 '12 at 08:03
  • imagine huge model with hundreds classes involved. Thx to the mighty Json.Net - I'm able to serialize it with no additional coding/attributes/etc, only applying correct setting to the serializer itself. So when I deserialize it with same settings I expect to get same results. It's impossible for me to correct my serializtion strategy, this is not the case, when I can easily go and put needed attributes. Looks like Json.Net is not working as expected. – Konstantin Chernov Sep 27 '12 at 08:10
  • @RogerTaylor I think JSon.Net is working as expected. By just looking at `PropertWithBackingField` property it has no way of knowing that it internally sets the private field `_privateFieid`. – L.B Sep 27 '12 at 08:20
  • Considering the resulting json - I don't see any problem. It shoul be possible for deserializer to see that property has a backing field and avoid duplicates – Konstantin Chernov Sep 27 '12 at 08:25
  • Backing with which field? What is its name? Where is this info? A property setter may even set two fields also. Put yourself to the place of the author of a serializer and think again. – L.B Sep 27 '12 at 08:29
  • Ok, looks like I'm indeed expecting too much. – Konstantin Chernov Sep 27 '12 at 08:30
-2

DeserializeInstance method receives existing instance as argument. Probably it does not creates new instance but fills in existing and returns it.

Try to put ReferenceEquals(instance, deserializedInstance) to your watch. To avoid data duplicating use overload that does not accepts existing instance or create new instance and deserialize it.

STO
  • 10,390
  • 8
  • 32
  • 32
  • Please read my post once again: `var deserializeInstance = JsonConvert.DeserializeObject(serializedInstance,typeof(ClassWithParams),new JsonSerializerSettings(){ContractResolver = contractResolver });` - existing instance is not involved. Only serialized one - a string actually. So I don't get the point of your answer. – Konstantin Chernov Sep 27 '12 at 07:24