2

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;
    }

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

5 Comments

I can't reproduce this either with Json.NET 6.0.8. @Corstiaan is likely doing JsonConvert.DeserializeObject<List<CacheItem<object>>> because s/he is deserializing a polymorphic list and is checking the type in CacheItem.Key before deserializing the value.
Thanks for both your time so far. Dbc is right -- I use <object> because I need to store many different types in the Value field of CacheItem. Also, I am on Xamarin developing a mobile app. Got the latest NuGet package of JSON.Net. Its very strange that you do not get the same error when calling JsonConvert.DeserializeObject<ReadabilitySettings>(list2.First().Value.ToString()); Any further ideas? I added a bit of extra code to my question
Oh OK, I never thought of that :). I've updated my answer to add an explanation of custom converters. It may help you with what you're trying to do, both with your version of JSON.net and helping to keep initialising all your object types more manageable. Are you able to try the same code as a generic Windows console application or something? Just to try and see if it's something weird going on with Xamarin.
Your edit looks promising. Thank you! I will look into it further tomorrow. I am building a mobile json caching system so I some some form of modularity as I need to be able to store a great variety of types. I will try to find a way to adapt your method a bit. Any suggestions?
Oh OK, I was working with a few known times but with various nuances when initialising them (e.g., I did a few more bits of set-up in ReadJson after I had everything deserialised). If you need it to work with a huge amount of types then perhaps the custom converter isn't for you - however I'm by far an expert with it. I've just tried your T Get<T>(string key) method and it also works fine for me. Could you have a look and post the innerException(s) you get when the "Error setting value to 'ReadabilityEnabled" exception is thrown? They may well point to the true cause.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.