Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I was tracking down a bug and I noticed that Newtonsoft JSON will append items to a List<> that's been initialized in the default constructor. I did a little more digging and discussed with some people on the C# chat and we noticed that this behavior doesn't apply to all other collection types.

https://dotnetfiddle.net/ikNyiT

using System;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Collections.ObjectModel;

public class TestClass
{
    public Collection<string> Collection = new Collection<string>(new [] { "ABC", "DEF" });
    public List<string> List = new List<string>(new [] { "ABC", "DEF" });
    public ReadOnlyCollection<string> ReadOnlyCollection = new ReadOnlyCollection<string>(new [] { "ABC", "DEF" });
}

public class Program
{
    public static void Main()
    {
        var serialized = @"{
            Collection: [ 'Goodbye', 'AOL' ],
            List: [ 'Goodbye', 'AOL' ],
            ReadOnlyCollection: [ 'Goodbye', 'AOL' ]
        }";


        var testObj = JsonConvert.DeserializeObject<TestClass>(serialized);

        Console.WriteLine("testObj.Collection: " + string.Join(",", testObj.Collection));
        Console.WriteLine("testObj.List: " + string.Join(",", testObj.List));
        Console.WriteLine("testObj.ReadOnlyCollection: " + string.Join(",", testObj.ReadOnlyCollection));
    }
}

Output:

testObj.Collection: ABC,DEF
testObj.List: ABC,DEF,Goodbye,AOL
testObj.ReadOnlyCollection: Goodbye,AOL

As you can see the Collection<> property is unaffected by deserialization, the List<> is appended to and ReadOnlyCollection<> is replaced. Is this intended behavior? What was the reasoning?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
128 views
Welcome To Ask or Share your Answers For Others

1 Answer

It basically boils down to type instantiation and the ObjectCreationHandling setting. There are three settings for ObjectCreationHandling

Auto 0 Reuse existing objects, create new objects when needed.
Reuse 1 Only reuse existing objects.
Replace 2 Always create new objects.

The default is auto (Line 44).

Auto is only overwritten after a series of checks which determine if the current type has a TypeInitializer which is null. At that point it checks if there is a parameterless constructor.

///
/// Create a factory function that can be used to create instances of a JsonConverter described by the
/// argument type.
/// The returned function can then be used to either invoke the converter's default ctor, or any
/// parameterized constructors by way of an object array.
///

Essentially it acts like this (what it looks like is about 1500 lines of code in 6 classes).

ObjectCreationHandling och = ObjectCreationHandling.Auto;
if( typeInitializer == null )
{
 if( parameterlessConstructor )
 {
  och = ObjectCreationHandling.Reuse;
 }
 else
 {
  och = ObjectCreationHandling.Replace;
 }
}

This setting is a part of the JsonSerializerSettings which are composed inside of the visitor pattern constructor for DeserializeObject. As shown above, each setting has a different function.

Getting back to List, Collection, and ReadOnlyCollection, we will look at the set of conditional statements for each.

List

testObj.List.GetType().TypeInitializer == null is false. As a result, List receives the default ObjectCreationHandling.Auto and the instantiated List for the testObj instance is used during deserialization, as well as a new List being instantiated with the serialized string.

testObj.List: ABC,DEF,Goodbye,AOL

Collection

testObj.Collection.GetType().TypeInitializer == null is true indicating there was no reflected type initializer available, so we go to the next condition which is to check if there is a parameterless constructor. testObj.Collection.GetType().GetConstructor(Type.EmptyTypes) == null is false. As a result Collection receives the value of ObjectCreationHandling.Reuse (only reuse existing objects). The instantiated instance for Collection is used from testObj, but the serialized string is not able to be instantiated.

testObj.Collection: ABC,DEF

ReadOnlyCollection

testObj.ReadOnlyCollection.GetType().TypeInitializer == null is true indicating there was no reflected type initializer available, so we go to the next condition which is to check if there is a parameterless constructor. testObj.ReadOnlyCollection.GetType().GetConstructor(Type.EmptyTypes) == null is also true. As a result ReadOnlyCollection recieves the value of ObjectCreationHandling.Replace (always create new objects). Only the instantiated value from the serialized string is used.

testObj.ReadOnlyCollection: Goodbye,AOL

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...