Home > Software engineering >  Deserializing an ordered custom collection
Deserializing an ordered custom collection

Time:06-14

I have a class, which has as a property a collection of instances of another class:

class Inventory
{
    public List<InventoryItem> Items
    {
        get;
        set;
    }
}

InventoryItem has a few properties, including a DateTime Start, but I will omit its implementation for brevity.

The class above deserializes correctly out of the box, with Newtonsoft.Json:

string serialization = "{ 'Items': [ { 'Amount': 1, 'Start': '2018-01-14T00:00:00', 'Id': 0 } ] }";
var inventory = JsonConvert.DeserializeObject<Inventory>( serialization );
Assert.AreEqual( inventory.Items.First().Amount, 1 );

However, what I want is to sort Items, by Start, as follows:

class Inventory
{
    private List<InventoryItem> _inventoryItems = new();

    public List<InventoryItem> Items
    {
        // order them before returning
        get => this._inventoryItems.OrderBy( t => t.Start ).ToList();
        set => this._inventoryItems = value;
    }
}

Now the deserialization fails. The Inventory class is created, but no inventory items are created. I can make this work with a custom converter, but it seems to me there ought to be an easier way.

CodePudding user response:

My best guess as to what's happening here is Json.NET is setting your property to a new instance of List<InventoryItem> and using Add to add each item to the list.

In your getter, you're ordering it then using .ToList() to convert it back to a list. This creates a new instance of List<InventoryItem>(), which means the items are getting added to a different list each time, all of which are being thrown away.

However, List<T> does have a .Sort() method you can utilize. Unfortunately though it returns void, so we can't make it a one-liner. In your case, the implementation of this would look a little like this:

class Inventory
{
    private List<InventoryItem> _inventoryItems = new();

    public List<InventoryItem> Items
    {
        // order them before returning
        get 
        {
            this._inventoryItems.Sort((x, y) => x.Start > y.Start ? 1 : -1);
            return this._inventoryItems;
        }
        set => this._inventoryItems = value;
    }
}

A little bit of an explanation on the Sort() method; It will always sort ascending. Returning 1 means that x is greater than y, returning -1 means that y is greater than x, and returning 0 means they're equal. In this example, it's sorting by Start ascending. To sort descending, you can either flip the sign or flip the values in the ternary statement.

  • Related