Need help serializing a Dictionary<string, object> using Json

Hello, I’m using an asset save system and it works, however I’m trying to mess with it and things start to go wrong. Right now I’m trying to switch from BinaryFormatter to Json.

As I understand the base of the system is a Dictionary which stores pretty much everything; here’s how a new Save File is created:

public static void SaveNew()
       SaveablePrefabs.UpdateTable(); //not sure it matters for the current issue
       Dictionary<string, object> state = new();
       SaveState(state);
       SaveFile(state);

here’s SaveFIle:

static void SaveFile(object state)
        {
            //this is old BF method (which works), ive commented it out
            //using (var stream = File.Open(FullSavePath, FileMode.Create))
            //{
            //    var formatter = new BinaryFormatter();
            //    formatter.Serialize(stream, state);
            //}
            
            //here's what im trying to do
            //using Newtonsoft Json 3.2.1 to convert
            var saveDict = JsonConvert.SerializeObject(state);
            File.WriteAllText(FullSavePath, saveDict);            
        }

deseriazing is basically this:

     string saveDict = File.ReadAllText(FullSavePath);
     Dictionary<string, object> newDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(saveDict);

It saves fine, but on loading it gives ‘Specified cast is not valid’ on this line:

var stateDictionary = (Dictionary<string, object>)state;

After like 3 days of thinking and debugging it looks like the value of the dict (which is Object) gets changed during serialization process and can’t be used anymore. So do i need to serialize it as well? If so how can it be done? I may be wrong of course, since I have little experience with dictionaries and serialization in general.

So how to properly serialize this kind of dictionary with jason? I’m open to all kinds of suggestions (maybe a different formatting method), though I’m not sure if it’s possible to change the dictonary itself, since it’s gonna break pretty much the whole system.

Set a breakpoint, attach the debugger and see for yourself what kind of type state really is.

Unless you require to have state be an object use a generic so that you can work with concrete types, ie
static void SaveFile<T>(T state) where T: new

Not sure about the where clause, just a best guess since serializers will want to instantiate the target type.

Also check the resulting json. Best to add at least one data entry. That’ll tell you whether the serialization wrote the correct format.

Tip: The var keyword helps you avoid repeating yourself:
var newDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(saveDict);

Note: All file I/O operations need to be enclosed in try/catch. They can fail, and they will fail for some users, be it lack of permissions, some other application having a lock on the file, running out of disk space, etc.

1 Like

Here’s an image form VS (its pretty small, my apologies), looks like Values not implemented.
I also checked the file (lots of times), it seems legit; I did a save, then deserialized the dictionary, serialized it back, saved again, and it’s still the same file. It looks like changing dict to string causes Object part of it to change.

I’m not sure where to implement generics, since I’m still figuring out the system.
I’m not asking anyone to go through the whole thing, but if someone has the time, I’ll post it here:

(the error occurs at 384)

Also thanks for the advice, I was having concerns about corrupted saves, try/catch should help.

Well, you serialize your dictionary as untyped data. However your loading code expects that dictionary to contain your specific data types like “ObjectMetadata”. Why would the deserializer create an instance of your specific class?

If you really want to use this polymorphic untyped approach, you would need to serialize your data with type information as shown here. by using

TypeNameHandling = TypeNameHandling.All

in the options.

Though it usually makes more sense to use properly typed structures which you serialize so you don’t need to store type information.

As alternative you may just not use any custom types at all and just rely on pure json types for everything. This may require more composing when saving the data and decomposing when deserializing it, but has the highest flexibility when it comes to the data.

Personally I use my own SimpleJSON (which is just a single C# file) in such cases. It does not do any object or struct mapping for you. It just parses the data into internal classes that represent the supported json types (object, array, number, boolean, string, null) and provides easy and convenient access and auto conversion. I also have a couple of “extension” files which add automatic conversion for certain types. The Unity extension adds support for the vector types and a couple of others.

With that approach modules can simply define for themselfs what and how they want to store the data. So each module gets a JSONObject and can add whatever it needs to it in the save method. The load method would get the same JSONObject and can read the data back. So the reading of the individual values would have to be done “manually”, but you can even add your own extensions for your own custom types if you like. It’s quite extensible in this regard. Though I can understand that many want to stick to the powerhouse aka Json .NET. It has many options, converter interfaces and what not. I like to keep things simple :slight_smile:

My Unity extension can store Vector3 values either as an object with x,y,z components or as array which is more compact in json.

2 Likes

This is too bloody difficult. So I’ve tried using serializer settings, I’ve serialized the dict, however I’ve no idea how to deserrialize:

var saveDict = ((Dictionary<string, object>)JsonConvert.DeserializeObject(saveDictString)).

and not the faintest what to do next. My guess Value somehow has to be specifically set to be of Type Object, unfortuantely the example only covers the List and I couldnt find one for my specific Dictionary.

I absolutely wouldn’t mind not using jason.net, in fact I’m hoping to not deal with formatters again. I actually am considering keeping BinaryFormatter, since it seems to work just fine, however I’ve spent too much time on this, plus I’ve read all the horrors about bf.

So would you please explain how SimpleJson work? I stick a script somewhere in the project, add it to namespace and how exactly do I serialize/deserialize my specific dictionary? Feel free to dumb it down :slight_smile:

You need to deserialise with the same settings that you used to serialise with. Otherwise it has no idea how to deserialise with the manner in which you serialised it.

2 Likes

:person_facepalming: Okay thanks, I think it works. I thought I needed to do smth like this

List<IAnimal> animalsBack = ((JArray)JsonConvert.DeserializeObject(json))
.Select(o => (IAnimal)JsonConvert.DeserializeObject(o.ToString(), 
Type.GetType((string)o["$type"]))).ToList();

And all I had to do was this:

JsonConvert.DeserializeObject<Dictionary<string, object>>(saveDictString, jsonSerializerSettings);

I need a break. I should probably mark Bunny’s post as a solution.

1 Like