There is a known issue using Json.NET to deserialize Stack<T>
which is explained in
The behavior reported is that Stack<T>
is reversed on deserialization, and the suggested solution is to use a custom JsonConverter
such as the one shown here.
Unfortunately it appears that solution does not work for a get-only stack property such as yours. Even if I specify a working converter for Stack<T>
the get-only property comes back empty. See .Net fiddle #1 for a demo of the problem.
So, why does this happen? Because Stack<T>
implements IEnumerable<T>
but not ICollection<T>
, the JsonArrayContract
constructor interprets Stack<T>
as a read-only collection that must be constructed via its parameterized constructor. Then, later, when attempting to deserialize your get-only, pre-allocated Stack<T>
property, JsonSerializerInternalReader.CalculatePropertyDetails()
decides the pre-existing value cannot be used (because it's a read-only collection) and since a newly allocated collection cannot be set back (because the property is not writable) the JSON property must be skipped. This, of course, doesn't account for the possibility that a JsonConverter
if present might be able to populate the collection somehow, even though Json.NET cannot.
This feels like a bug to me; you might report an issue to Newtonsoft that a get-only Stack<T>
property cannot be deserialized even with a custom JsonConverter
.
As a workaround that preserves the immutability of your Stack<T>
property, in addition to creating a custom JsonConverter
for Stack<T>
, you can create a parameterized constructor for your Game
that takes a Stack<Card> cards
argument, and mark it with JsonConstructor
:
public class Game
{
public Game() { this.cards = new Stack<Card>(); }
[JsonConstructor]
Game(Stack<Card> cards)
{
this.cards = cards ?? new Stack<Card>();
}
readonly Stack<Card> cards;
public Stack<Card> Cards { get { return cards; } }
}
Note that the constructor argument name must be the same as the JSON property name, modulo case.
(Alternatively, you could make the property be privately settable and mark it with [JsonProperty]
; this destroys the guarantee of immutability however.)
Then to deserialize, do:
var settings = new JsonSerializerSettings
{
Converters = { new StackConverter() },
};
var unser = JsonConvert.DeserializeObject<Game>(ser, settings);
Where StackConverter
is taken verbatim from this answer.
Demo .Net fiddle #2 showing that the workaround does work.