Corstiaan
Corstiaan

Reputation: 1114

JSON.Net deserialization error setting value to property

I want so serialize/deserialize the following model:

public class ReadabilitySettings
{
    public ReadabilitySettings() {
    }

    private bool _reababilityEnabled;
    public bool ReadabilityEnabled {
        get {
            return _reababilityEnabled;
        }
        set {
            _reababilityEnabled = value;
        }
    }

    private string _fontName;
    public string FontName { 
        get {
            return _fontName;
        }
        set {
            _fontName = value;
        }
    }

    private bool _isInverted;
    public bool IsInverted {
        get {
            return _isInverted;
        }
        set {
            _isInverted = value;
        }
    }

    public enum FontSizes
    {
        Small = 0,
        Medium = 1,
        Large = 2
    }

    private FontSizes _fontSize;
    public FontSizes FontSize { get
        {
            return _fontSize;
        }
        set 
        { 
            _fontSize = value;
        }
    }
}
}

I have a list containing instances of the following object:

public class CacheItem<T>
{
    public string Key { get; set; }
    public T Value { get; set; }
}

I populate the list like so:

list.Add(new CacheItem<ReadabilitySettings>() { Key = "key1", Value = InstanceOfReadabilitySettings };

When I want to serialize this list I call:

var json = JsonConvert.SerializeObject (list);

This works fine. No errors. It gives the following json:

[{"Key":"readbilitySettings","Value":{"ReadabilityEnabled":true,"FontName":"Lora","IsInverted":true,"FontSize":2}}]

When I want to deserialize the list I call:

var list = JsonConvert.DeserializeObject<List<CacheItem<object>>> (json);

This gives me a list of CacheItem's with it's Value property set to a JObject. No errors so far.

When I want the actual instance of ReadabilitySettings I call:

var settings = JsonConvert.DeserializeObject<ReadabilitySettings> (cacheItem.Value.ToString ());

I have to call this since the cacheItem.Value is set to a json string, not to an instance of ReadabilitySettings. The json string is:

{{   "ReadabilityEnabled": true,   "FontName": "Lora",   "IsInverted": true,   "FontSize": 2 }} Newtonsoft.Json.Linq.JObject

Then I get this error: "Error setting value to 'ReadabilityEnabled' on 'Reflect.Mobile.Shared.State.ReadabilitySettings'."

What am I missing? Thanks!

EDIT------

This is the method that throws the error:

    public T Get<T> (string key)
    {
        var items = GetCacheItems (); // this get the initial deserialized list of CacheItems<object> with its value property set to a JObject
        if (items == null)
            throw new CacheKeyNotFoundException ();
        var item = items.Find (q => q.Key == key);
        if (item == null)
            throw new CacheKeyNotFoundException ();
        var result = JsonConvert.DeserializeObject<T> (item.Value.ToString ()); //this throws the error
        return result;
    }

Upvotes: 2

Views: 6705

Answers (2)

Corstiaan
Corstiaan

Reputation: 1114

Managed to solve it. Sample code did not show that ReadabilitySettings also implemented the INotifyPropertyChanged interface. The subsequent eventhandler wired-up to the PropertyChanged event of ReadabilitySettings somewhere else in the project had some errors and thus the deserializer was not able to instantiate ReadabilitySettings :-).

Not a glamorous save but its working... Thanks for your time.

Upvotes: 0

akiller
akiller

Reputation: 2472

I've just tried this, which is pretty much copy/pasting your code and it works fine using Newtonsoft.Json v6.0.0.0 (from NuGet):

var list = new List<CacheItem<ReadabilitySettings>>();
list.Add(new CacheItem<ReadabilitySettings>() { Key = "key1", Value = new ReadabilitySettings() { FontName = "FontName", FontSize = ReadabilitySettings.FontSizes.Large, IsInverted = false, ReadabilityEnabled = true } });

var json = JsonConvert.SerializeObject(list);
var list2 = JsonConvert.DeserializeObject<List<CacheItem<object>>>(json);
var settings = JsonConvert.DeserializeObject<ReadabilitySettings>(list2.First().Value.ToString());

However, you don't need the last line. Simply switch List<CacheItem<object>> to List<CacheItem<ReadabilitySettings>> in your call to DeserializeObject and it automatically resolves it:

var list2 = JsonConvert.DeserializeObject<List<CacheItem<ReadabilitySettings>>>(json);

Now list2.First().Value.GetType() = ReadabilitySettings and there's no need to do any further deserialising. Is there a reason you're using object?


Edit:

I'm not sure if this helps you necessarily, but given what you're trying to do have you thought about using a custom converter? I had to do this a few days ago for similar reasons. I had an enum property coming back in my JSON (similar to your key) which gave a hint as to what type a property in my JSON and therefore my deserialised class was. The property in my class was an interface rather than an object but the same principle applies for you.

I used a custom converter to automatically handle creating the object of the correct type cleanly.

Here's a CustomConverter for your scenario. If your key is set to readbilitySettings then result.Value is initialised as ReadabilitySettings.

public class CacheItemConverter : CustomCreationConverter<CacheItem<object>>
{
    public override CacheItem<object> Create(Type objectType)
    {
        return new CacheItem<object>();
    }

    public CacheItem<object> Create(Type objectType, JObject jObject)
    {
        var keyProperty = jObject.Property("Key");
        if (keyProperty == null)
            throw new ArgumentException("Key missing.");

        var result = new CacheItem<object>();

        var keyValue = keyProperty.First.Value<string>();
        if (keyValue.Equals("readbilitySettings", StringComparison.InvariantCultureIgnoreCase))
            result.Value = new ReadabilitySettings();
        else
            throw new ArgumentException(string.Format("Unsupported key {0}", keyValue));

        return result;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JObject jObject = JObject.Load(reader);
        var target = Create(objectType, jObject);
        serializer.Populate(jObject.CreateReader(), target);

        /* Here your JSON is deserialised and mapped to your object */

        return target;
    }
}

Usage:

var list = JsonConvert.DeserializeObject<List<CacheItem<object>>>(json, new CacheItemConverter());

Here's a link to a full working example of it: http://pastebin.com/PJSvFDsT

Upvotes: 2

Related Questions