Search code examples
c#windows-phone-8json.netdeserializationisolatedstorage

JSON.net deserialization error on load from Isolated Storage


I am working on an app that saves information about individual runs (Name, date, distance, time, type) into an ObservableCollection.

I've got a viewmodel set up to display this information in a LongListSelector. I've got no problem getting the data into the model, and no problems (at least no errors) saving that information -- for as long as the app is running. I get an exception when restarting the app and attempting to load that information by deserializing it with newtonsoft.json

I am getting the error when trying to the data that was previously serialized and saved to IsolatedStorageSettings.ApplicationSettings

The error message is: 'System.Reflection.TargetInvocationException' in mscorlib.ni.dll

Edit responding to comment seeking more info I'll work on getting the rest of that. I'm new to heavy debugging. The error description is:

Newtonsoft.Json.JsonSerializationException: Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Run_Ranger.ViewModels.RunSaveMain' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly. To fix this error either change the JSON to a JSON object (e.g. {"name":"value"}) or change the deserialized type to an array or a type that implements a collection interface (e.g. iCollection, IList) like List that can be deserialized from a JSON array. JsonArrayAttribute can also be added to the type to force it to deserialize from a JSON array.

Path", line1, position1. at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.EnsureArrayContract(JsonReader reader, Type objectType, JsonContract contract)

at...

and more I can't see. for some reason, I can catch the exception and display it in a messagebox on the emulator (from which I typed that, above, but it's truncated) but when I use debug.writeline, I'm not getting anything in the output window (debug) more than: Exception thrown: 'Newtonsoft.Json.JsonSerializationException' in Newtonsoft.Json.DLL

It occurs at this line in my code.

    private RunSaveMain GetSavedRunsFromMemory() // is this all I need to stick data into the model?
    {
        //ClearAll();
        RunSaveMain data;
        string dataFromAppSettings;

        if (IsolatedStorageSettings.ApplicationSettings.TryGetValue(SaveRunsKey, out dataFromAppSettings))
        {
            data = JsonConvert.DeserializeObject<RunSaveMain>(dataFromAppSettings); //<--- ERROR HERE
        }
        else
        {
            data = new RunSaveMain();
            data.Title = AppResources.SavedRuns;
        }

        return data;
    }

I seem to have no trouble serializing and saving the data to storage. When I debug, I can see it in there, the value looking like this example:

[{"RunName":"Steamtown Marathon 2015","RunType":"real","RunDistMiles":26.2,"RunDate":"2015-10-18T00:00:00","RunTimeTotalSecs":10241.0},{"RunName":"Marine Corps Marathon 2015","RunType":"real","RunDistMiles":26.2,"RunDate":"2015-10-25T00:00:00","RunTimeTotalSecs":10500.0}]

I serialize and save it right after each addition of an item, like so:

private void RunNameCompleted(object sender, string runName, string runType)
    {
        // name string has already been checked 
        // create a rundata object
        // put in as much info as we want
        RunSaveItem RunToSave = new RunSaveItem();
        RunToSave.RunName = runName; // various values passed in
        RunToSave.RunDate = CurrentPaceMath.Rundate;
        RunToSave.RunType = runType;
        RunToSave.RunTimeTotalSecs = CurrentPaceMath.TimeTotalSecsIn;
        RunToSave.RunDistMiles = CurrentPaceMath.MilesIn;
        App.RunSaveItemView.Items.Add(RunToSave); // // add the rundata to app.viewmodels.runmodel

        // save the list of runs to isolatedstorage.applicationSettings                                          
        var data = JsonConvert.SerializeObject(App.RunSaveItemView.Items);
        IsolatedStorageSettings.ApplicationSettings[RunSaveMain.SaveRunsKey] = data;
        IsolatedStorageSettings.ApplicationSettings.Save();

        NavigationService.Navigate(new Uri("/MainPage.xaml", UriKind.RelativeOrAbsolute));
    }

I wondered if there might be an issue converting the datetime values, so I ran it again without those, and it still crashed at the same line.

The class is set up like this:

public class RunSaveItem : INotifyPropertyChanged
{
    private string _runName;
    /// <summary>
    /// Name of the run
    /// </summary>
    public string RunName
    {
        get
        {
            return _runName;
        }
        set
        {
            if (value != _runName)
            {
                _runName = value;
                NotifyPropertyChanged("RunName");
            }
        }
    }

    private string _runType;
    /// <summary>
    /// Type of run
    /// </summary>
    public string RunType
    {
        get
        {
            return _runType;
        }
        set
        {
            if (value != _runType)
            {
                _runType = value;
                NotifyPropertyChanged("RunType");
            }
        }
    }

    private double _runDistMiles;
    /// <summary>
    /// Run distance in miles
    /// </summary>
    public double RunDistMiles
    {
        get
        {
            return _runDistMiles;
        }
        set
        {
            if (value != _runDistMiles)
            {
                _runDistMiles = value;
                NotifyPropertyChanged("RunDistMiles");
            }
        }
    }

    private DateTime _runDate;
    /// <summary>
    /// Date of the run. There will be no time, or rather, time = 00:00:00 as it's selected with a datepicker.
    /// </summary>
    public DateTime RunDate
    {
        get
        {
            return _runDate;
        }
        set
        {
            if (value != _runDate)
            {
                _runDate = value;
                NotifyPropertyChanged("RunDate");
            }
        }
    }

    private double _runTimeTotalSecs;
    /// <summary>
    /// Duration of the run expressed in seconds
    /// </summary>
    public double RunTimeTotalSecs
    {
        get
        {
            return _runTimeTotalSecs;
        }
        set
        {
            if (value != _runTimeTotalSecs)
            {
                _runTimeTotalSecs = value;
                NotifyPropertyChanged("RunTimeTotalSecs");
            }
        }
    }

I did a bunch of searching and the only comparable errors I could find seemed to stem from trying to deserialize outside data into the wrong data types. Since I'm using JSON to serialize the right data types directly from the model in the first place, I don't think that's my issue, but I'm stuck for what it might be.

Anyone seen this before?


Solution

  • You are serializing a list of RunSaveItem here:

        // save the list of runs to isolatedstorage.applicationSettings                                          
        var data = JsonConvert.SerializeObject(App.RunSaveItemView.Items);
        IsolatedStorageSettings.ApplicationSettings[RunSaveMain.SaveRunsKey] = data;
        IsolatedStorageSettings.ApplicationSettings.Save();
    

    But you are trying to deserialize a single object (not a list) of type RunSaveMain. These types are inconsistent. What's worse, Json.NET serializes collections as JSON arrays while other POCOs are serialized as JSON objects. The difference is explained in the JSON standard:

    • An array is an ordered collection of values. An array begins with [ (left bracket) and ends with ] (right bracket). Values are separated by , (comma).

    • An object is an unordered set of name/value pairs. An object begins with { (left brace) and ends with } (right brace).

    This mismatch accounts for the exception message you are seeing:

    Cannot deserialize the current JSON array (e.g. [1,2,3]) into type 'Run_Ranger.ViewModels.RunSaveMain' because the type requires a JSON object (e.g. {"name":"value"}) to deserialize correctly.

    Instead, you need to deserialize the saved data as a collection of the same type:

    private List<RunSaveItem> GetSavedRunsFromMemory()
    {
        //ClearAll();
        List<RunSaveItem> data;
        string dataFromAppSettings;
    
        if (IsolatedStorageSettings.ApplicationSettings.TryGetValue(SaveRunsKey, out dataFromAppSettings))
        {
            data = JsonConvert.DeserializeObject<List<RunSaveItem>>(dataFromAppSettings); 
        }
        else
        {
            data = new List<RunSaveItem>();
        }
    
        return data;
    }