Search Unity

C# Serialization + PlayerPrefs mystery

Discussion in 'Scripting' started by mindlube, Dec 29, 2010.

  1. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    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.
    Code (csharp):
    1. private string SerializeString (System.Object graphObj)
    2.     {
    3.         int count = 0;
    4.         byte[] byteArray;
    5.         char[] charArray;
    6.         ASCIIEncoding charEncoding = new ASCIIEncoding ();
    7.         MemoryStream stream = new MemoryStream (2048);
    8.         BinaryFormatter formatter = new BinaryFormatter ();
    9.        
    10.         // serialize the object into a stream
    11.         formatter.Serialize (stream, graphObj);
    12.         stream.Seek (0, SeekOrigin.Begin);
    13.         // convert to byte array
    14.         byteArray = new byte[stream.Length];
    15.         while (count < stream.Length)
    16.             byteArray[count++] = Convert.ToByte (stream.ReadByte ());
    17.         // convert to char array
    18.         charArray = new char[charEncoding.GetCharCount (byteArray, 0, count)];
    19.         charEncoding.GetDecoder ().GetChars (byteArray, 0, count, charArray, 0);
    20.         return new string (charArray);
    21.     }
    22.  
    23.     private System.Object DeserializeString (string serializedStr)
    24.     {
    25.         byte[] byteArray;
    26.         char[] charArray;
    27.         MemoryStream stream;
    28.         ASCIIEncoding charEncoding = new ASCIIEncoding ();
    29.         BinaryFormatter formatter = new BinaryFormatter ();
    30.        
    31.         // convert string into char array
    32.         charArray = serializedStr.ToCharArray ();
    33.         // char array into byte array
    34.         byteArray = new byte[charEncoding.GetByteCount (charArray)];
    35.         charEncoding.GetEncoder ().GetBytes (charArray, 0, charArray.Length, byteArray, 0, false);
    36.         // convert byte array into memorystream    
    37.         stream = new MemoryStream (byteArray);
    38.         stream.Seek (0, SeekOrigin.Begin);
    39.         // deserialize the memorystream into restored object       
    40.         return formatter.Deserialize( stream );
    41.     }
    42.    
     
    jpthek9 likes this.
  2. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    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 http://code.google.com/p/jsonsharp/
    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.
     
  3. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
  4. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    Oh sweet, this method of Base64 encoding to a string seems to work well with Unity and PlayerPrefs
    http://bytes.com/topic/c-sharp/answers/512819-serialize-deserialize-object-into-string

    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.
     
    jpthek9 likes this.
  5. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    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.
    Code (csharp):
    1. using UnityEngine;
    2. using System;
    3. using System.IO;
    4. using System.Runtime.Serialization;
    5. using System.Runtime.Serialization.Formatters.Binary;
    6.  
    7. public class PlayerPrefsSerializer  
    8. {
    9.     public static BinaryFormatter bf = new BinaryFormatter ();
    10.        // serializableObject is any struct or class marked with [Serializable]
    11.     public static void Save (string prefKey, object serializableObject)
    12.     {
    13.         MemoryStream memoryStream = new MemoryStream ();
    14.         bf.Serialize (memoryStream, serializableObject);
    15.         string tmp = System.Convert.ToBase64String (memoryStream.ToArray ());
    16.         PlayerPrefs.SetString ( prefKey, tmp);
    17.     }
    18.    
    19.     public static object Load (string prefKey)
    20.     {
    21.         string tmp = PlayerPrefs.GetString (prefKey, string.Empty);
    22.         if (tmp == string.Empty )
    23.             return null;
    24.         MemoryStream memoryStream = new MemoryStream (System.Convert.FromBase64String (tmp));
    25.         return bf.Deserialize (memoryStream);
    26.     }
    27. }
     
    Last edited: Jan 3, 2011
    Kitsuba and jpthek9 like this.
  6. Chris-Sinclair

    Chris-Sinclair

    Joined:
    Jun 14, 2010
    Posts:
    1,326
    Might as well throw a bit of generics in there to make your life easier by taking care of the casting:

    Code (csharp):
    1. public static object Load<T>(string prefKey)
    2. {
    3.     if (!PlayerPrefs.HasKey(prefKey))
    4.         return default(T);
    5.    
    6.     string serializedData = PlayerPrefs.GetString(prefKey);
    7.     MemoryStream dataStream = new MemoryStream(System.Convert.FromBase64String(serializedData));
    8.    
    9.     T deserializedObject = (T)bf.Deserialize(dataStream);
    10.    
    11.     return deserializedObject;
    12. }
    13.  
    14.  
    15. 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?
     
    Kitsuba and GibTreaty like this.
  7. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    great suggestions- thanks!
     
  8. sims11tz

    sims11tz

    Joined:
    Nov 20, 2011
    Posts:
    15
    This code was perfect, thank you!!!
     
  9. hexdump

    hexdump

    Joined:
    Dec 29, 2008
    Posts:
    443
    Thanks for the code man. I was searching for a fast way to serialize my data. You saved me the effort.

    Thanks in advance.
     
  10. Stephan-Tanguay

    Stephan-Tanguay

    Joined:
    Jan 27, 2011
    Posts:
    21
    Works great in the editor and mac, had some issues with iOS but got it working after I set the stripping level to UseMicroMSCorlib
     
    Last edited: Mar 13, 2013
  11. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    Flagging this thread to review later. Good stuff.
     
  12. BlackDragonBE

    BlackDragonBE

    Joined:
    Jul 23, 2012
    Posts:
    12
    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.
     
  13. mindlube

    mindlube

    Joined:
    Oct 3, 2008
    Posts:
    993
    Glad the code is useful, all! I am actually using https://code.google.com/p/protobuf-net/ for all my serialization needs now. Nothing wrong with BinaryFormatter, rather that protobuf-net is pretty lean mean :)
     
    terrypaton1 likes this.
  14. KEMBL

    KEMBL

    Joined:
    Apr 16, 2009
    Posts:
    181
    Also you can use this return type

    Code (csharp):
    1. public static T Load<T>(string prefKey)
     
  15. pretender

    pretender

    Joined:
    Mar 6, 2010
    Posts:
    865
    Do you store it later in playerprefs or on file? if it is in playerprefs then could you share how you do it?
    thanks!
     
  16. noosxe

    noosxe

    Joined:
    Aug 31, 2013
    Posts:
    1
    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
     
  17. Strangerweather

    Strangerweather

    Joined:
    Nov 11, 2015
    Posts:
    4
    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! :)
     
  18. 2Toad

    2Toad

    Joined:
    Jul 17, 2016
    Posts:
    1
    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:

    Code (CSharp):
    1. using System;
    2. using System.IO;
    3. using System.Runtime.Serialization.Formatters.Binary;
    4. using UnityEngine;
    5.  
    6. /// <summary>
    7. /// Store objects in PlayerPrefs
    8. /// </summary>
    9. static class Serializer {
    10.     /// <summary>
    11.     /// Serialize the <paramref name="item"/> and save it to PlayerPrefs under the <paramref name="key"/>.
    12.     /// <paramref name="item"/> class must have the [Serializable] attribute. Use the
    13.     /// [NonSerialized] attribute on fields you do not want serialized with the class.
    14.     /// </summary>
    15.     /// <param name="key">The key</param>
    16.     /// <param name="item">The object</param>
    17.     internal static void Save(string key, object item) {
    18.         using (var stream = new MemoryStream()) {
    19.             formatter.Serialize(stream, item);
    20.             var bytes = stream.ToArray();
    21.             var serialized = Convert.ToBase64String(bytes);
    22.             PlayerPrefs.SetString(key, serialized);
    23.         }
    24.     }
    25.  
    26.     /// <summary>
    27.     /// Load the <paramref name="key"/> from PlayerPrefs and deserialize it.
    28.     /// </summary>
    29.     /// <param name="key">The key</param>
    30.     internal static T Load<T>(string key) {
    31.         if (!PlayerPrefs.HasKey(key)) return default(T);
    32.  
    33.         var serialized = PlayerPrefs.GetString(key);
    34.         var bytes = Convert.FromBase64String(serialized);
    35.  
    36.         T deserialized;
    37.         using (var stream = new MemoryStream(bytes)) {
    38.             deserialized = (T)formatter.Deserialize(stream);
    39.         }
    40.  
    41.         return deserialized;
    42.     }
    43.  
    44.     static readonly BinaryFormatter formatter = new BinaryFormatter();
    45. }
    46.  
    Thanks @mindlube, @Chris Sinclair, and @KEMBL
     
    Last edited: Jul 17, 2016
    sp-LeventeLajtai and decapator like this.
  19. mahdie

    mahdie

    Joined:
    May 21, 2017
    Posts:
    7
    I'm really grateful for you @2Toad !!
     
  20. decapator

    decapator

    Joined:
    Sep 2, 2013
    Posts:
    10
    @2Toad I second that really grateful! thank you very very much!!