I've read here Is the order of elements in a JSON list preserved?
that the order in Json matters.
I've also read here Does List<T> guarantee insertion order? that the insert order in a C# generic list is guaranteed.
Can I therefor assume that when I use the newtonsoft JsonSerializer to read this Json
"answers": [
{
"choice": "36"
},
{
"choice": "50"
}
]
in an object with a property 'Answers' which is of type GenericList, that Answers[0] always returns 36 and Answers[1] always returns 50?
Or is it possible the JsonSerializer shuffles data around?
The reason I ask is that I read data from an external API and they say "you should only get 1 answer back, but when you get more, use the last one", and the last is the last one in text, so in this case the '50'.
Yes, you can rely on the order of array nodes read via the JsonSerializer
. A JSON array is defined to be an ordered collection of values., and Json.NET will add them to your collection in the order they are encountered in the JSON file.
This can be verified by checking the source code. The method JsonSerializerInternalReader.CreateList()
is the top-level method responsible for collection deserialization, and has three basic cases:
When deserializing to a read/write collection, ICollection.Add()
(or ICollection<T>.Add()
) will get called in the order in which values are read from the JSON stream, thereby preserving the JSON array order. This can be seen in JsonSerializerInternalReader.PopulateList()
.
(When the collection is missing a non-generic Add(object value)
method, a CollectionWrapper<T>
will get created to handle casting to the required argument type, however this does not affect the algorithm at all since the wrapper immediately calls the Add(T Value)
method of the underlying collection.)
When deserializing to a .Net array, a temporary List<T>
is created for some appropriate T
and values are added in the order encountered as in case 1, either through JsonSerializerInternalReader.PopulateList()
or JsonSerializerInternalReader.PopulateMultidimensionalArray()
. Subsequently the list is converted to an array afterwards by calling either Array.CreateInstance
then List<T>.ICollection.CopyTo(Array, Int32)
, or CollectionUtils.ToMultidimensionalArray()
. Both create the array preserving the order of values of the incoming collection.
When deserializing an immutable collection, the collection must have a constructor that takes an IEnumerable<T>
where T
is the collection item type. This is explained in the release notes for Json.NET 6.0.3:
To all future creators of immutable .NET collections: If your collection of T has a constructor that takes
IEnumerable<T>
then Json.NET will automatically work when deserializing to your collection, otherwise you're all out of luck.
Assuming your immutable collection has the required constructor, the algorithm proceeds as in case 2, deserializing to a List<T>
then constructing the immutable collection from the list with the values in the order they were encountered and deserialized.
Of course, the collection itself might shuffle the order of values:
When deserializing to a SortedSet<T>
the values will be dynamically re-ordered by the collection comparer.
When deserializing to a Stack<T>
there is a known issue in which the stack order is reversed on deserialization: Issue #971: JsonConvert.DeserializeObject<Stack<T>>/JsonConvert.Serialize(Stack<T)
does not work as expected.
The problem here is that Stack<T>
implements IEnumerable<T>
but not ICollection<T>
, so Json.NET thinks it is an immutable collection. Fortunately, it has the required constructor taking a single IEnumerable<T>
input argument. Unfortunately, since it's a stack, it reverses the order of inputs when enumerated.
For details see JsonConvert.Deserializer indexing issues.
However for the basic collections List<T>
or T []
this will not occur.