C# Serialization + PlayerPrefs mystery

Hi all, I’m banging my head on this c# problem. It might be a character encoding problem with PlayerPrefs. Goal: some structs are marked as [Serializable] so I want to save them out to disk using Unity’s playerprefs.

Here are my 2 methods. The Serialize one is basically lifted from the MSDN example for memorystream usage. The Deserialize is my attempt to write the converse. A couple of observations

  1. they work, provided I use the ASCIIEncoding. If I use the UnicodeEncoding as in the MSDN docs, I get exceptions
  2. If I take the resultant ASCIIencoded string and put it into PlayerPrefs, it magically turns into an empty string when I fetch it back to from playerprefs. Very bizarre.

Thanks if anyone can point out what is wrong with these methods, possible incompatibilities with PlayerPrefs.SetString string encoding, or any community examples of what I’m trying to accomplish, it would be much appreciated! Thanks.

private string SerializeString (System.Object graphObj)
	{
		int count = 0;
		byte[] byteArray;
		char[] charArray;
		ASCIIEncoding charEncoding = new ASCIIEncoding ();
		MemoryStream stream = new MemoryStream (2048);
		BinaryFormatter formatter = new BinaryFormatter ();
		
		// serialize the object into a stream
		formatter.Serialize (stream, graphObj);
		stream.Seek (0, SeekOrigin.Begin);
		// convert to byte array
		byteArray = new byte[stream.Length];
		while (count < stream.Length)
			byteArray[count++] = Convert.ToByte (stream.ReadByte ());
		// convert to char array
		charArray = new char[charEncoding.GetCharCount (byteArray, 0, count)];
		charEncoding.GetDecoder ().GetChars (byteArray, 0, count, charArray, 0);
		return new string (charArray);
	}

	private System.Object DeserializeString (string serializedStr)
	{
		byte[] byteArray;
		char[] charArray;
		MemoryStream stream;
		ASCIIEncoding charEncoding = new ASCIIEncoding ();
		BinaryFormatter formatter = new BinaryFormatter ();
		
		// convert string into char array
		charArray = serializedStr.ToCharArray ();
		// char array into byte array
		byteArray = new byte[charEncoding.GetByteCount (charArray)];
		charEncoding.GetEncoder ().GetBytes (charArray, 0, charArray.Length, byteArray, 0, false);
		// convert byte array into memorystream		
		stream = new MemoryStream (byteArray);
		stream.Seek (0, SeekOrigin.Begin);
		// deserialize the memorystream into restored object		
		return formatter.Deserialize( stream );
	}
1 Like

Can anyone recommend a lightweight JSON serialization library for C#? Preferably

  1. use reflection and preferably be aware of [Serializable] flag in C#
  2. not be limited to a particular set of datatypes. e.g. this looks pretty limited and not very useful Google Code Archive - Long-term storage for Google Code Project Hosting.
  3. Runs well with Unity. This one throws exceptions on the most limited tasks http://litjson.sourceforge.net/

Thanks for any suggestions you may have.

Ooh this one looks good Google Code Archive - Long-term storage for Google Code Project Hosting.
testing…

Oh sweet, this method of Base64 encoding to a string seems to work well with Unity and PlayerPrefs

using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

MemoryStream memoryStream = new MemoryStream();
BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, targetObject);
string str = System.Convert.ToBase64String(memoryStream.ToArray ());

where targetObject is the object that you are trying to serialize.

1 Like

Here is my class - maybe it will be useful for someone else? This does create dependency on System.Core.dll which is only 6KB however- a small price to pay for the convenience.

using UnityEngine;
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

public class PlayerPrefsSerializer  
{
	public static BinaryFormatter bf = new BinaryFormatter ();
       // serializableObject is any struct or class marked with [Serializable]
	public static void Save (string prefKey, object serializableObject)
	{
		MemoryStream memoryStream = new MemoryStream ();
		bf.Serialize (memoryStream, serializableObject);
		string tmp = System.Convert.ToBase64String (memoryStream.ToArray ());
		PlayerPrefs.SetString ( prefKey, tmp);
	}
	
	public static object Load (string prefKey)
	{
		string tmp = PlayerPrefs.GetString (prefKey, string.Empty);
		if (tmp == string.Empty )
			return null;
		MemoryStream memoryStream = new MemoryStream (System.Convert.FromBase64String (tmp));
		return bf.Deserialize (memoryStream);
	}
}
2 Likes

Might as well throw a bit of generics in there to make your life easier by taking care of the casting:

public static object Load<T>(string prefKey)
{
	if (!PlayerPrefs.HasKey(prefKey))
		return default(T);
	
	string serializedData = PlayerPrefs.GetString(prefKey);
	MemoryStream dataStream = new MemoryStream(System.Convert.FromBase64String(serializedData));
	
	T deserializedObject = (T)bf.Deserialize(dataStream);
	
	return deserializedObject;
}


MyCustomClass myInstance = Load<MyCustomClass>("some pref key");

Also, just tweaked some of the code itself (that’s just my styling). Does your “bf” BinaryFormatter need to be public?

2 Likes

great suggestions- thanks!

This code was perfect, thank you!!!

Thanks for the code man. I was searching for a fast way to serialize my data. You saved me the effort.

Thanks in advance.

Works great in the editor and mac, had some issues with iOS but got it working after I set the stripping level to UseMicroMSCorlib

Flagging this thread to review later. Good stuff.

This helped me so much, you have no idea!
It was exactly what I was looking for, I’m doing inventory loading/saving and this works quite efficiently.

Glad the code is useful, all! I am actually using Google Code Archive - Long-term storage for Google Code Project Hosting. for all my serialization needs now. Nothing wrong with BinaryFormatter, rather that protobuf-net is pretty lean mean :slight_smile:

1 Like

Also you can use this return type

public static T Load<T>(string prefKey)

Do you store it later in playerprefs or on file? if it is in playerprefs then could you share how you do it?
thanks!

The best would be to always use protobuf-net, although it has some tricky points to make work under iOS, but anyway in the end of serialization you have to base64 encode the bytes to store it as string in PlayerPrefs

I realize this is really old but does it still work? I am trying to serialize my PlayerPrefs, but when I use this, I see no difference in how the string looks in Registry. Any help would be welcome! :slight_smile:

I found the serialization feature of Unity3D’s JsonUtility to be too short sighted (e.g., it doesn’t serialize DateTime fields), and for my purpose Json.NET was overkill. I merged the examples in this thread together, modernized them a bit, and fixed some memory leaks:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEngine;

/// <summary>
/// Store objects in PlayerPrefs
/// </summary>
static class Serializer {
    /// <summary>
    /// Serialize the <paramref name="item"/> and save it to PlayerPrefs under the <paramref name="key"/>.
    /// <paramref name="item"/> class must have the [Serializable] attribute. Use the
    /// [NonSerialized] attribute on fields you do not want serialized with the class.
    /// </summary>
    /// <param name="key">The key</param>
    /// <param name="item">The object</param>
    internal static void Save(string key, object item) {
        using (var stream = new MemoryStream()) {
            formatter.Serialize(stream, item);
            var bytes = stream.ToArray();
            var serialized = Convert.ToBase64String(bytes);
            PlayerPrefs.SetString(key, serialized);
        }
    }

    /// <summary>
    /// Load the <paramref name="key"/> from PlayerPrefs and deserialize it.
    /// </summary>
    /// <param name="key">The key</param>
    internal static T Load<T>(string key) {
        if (!PlayerPrefs.HasKey(key)) return default(T);

        var serialized = PlayerPrefs.GetString(key);
        var bytes = Convert.FromBase64String(serialized);

        T deserialized;
        using (var stream = new MemoryStream(bytes)) {
            deserialized = (T)formatter.Deserialize(stream);
        }

        return deserialized;
    }

    static readonly BinaryFormatter formatter = new BinaryFormatter();
}

Thanks @mindlube , @Chris-Sinclair , and @KEMBL

2 Likes

I’m really grateful for you @2Toad !!

@2Toad I second that really grateful! thank you very very much!!