Search Unity

Can't Xml Serialize Enum: InvalidOperationException

Discussion in 'Scripting' started by MCrafterzz, Jul 19, 2017.

  1. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    354
    I'm working on saving game data and it workes fine except for enums. When trying to save a enum it gives me this error:
    InvalidOperationException: The type of the argument object 'Item.Type' is not primitive.

    Code:
    Item class:
    Code (CSharp):
    1. [Serializable]
    2.     public enum Type {normal, food, weapon, shield, armour, fishing_rod, pickaxe};
    3.     public Type type;
    Serialazation:
    Code (CSharp):
    1. XmlSerializer serializer = new XmlSerializer (typeof(DataHolder));
    2.         FileStream stream = new FileStream ("Assets/test.sav", FileMode.Create);
    3.         serializer.Serialize (stream, dataHolder);
    4.         stream.Close();
    An important notes is that I'm not saving the enum with the values only the instances of it which has one value. Thanks for any help!
     
  2. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    Filling in the blanks your example works fine:

    Code (csharp):
    1.  
    2. using System.IO;
    3. using System.Xml.Serialization;
    4. using UnityEngine;
    5.  
    6. public class SerializeTest : MonoBehaviour {
    7.  
    8.     Item item;
    9.  
    10.     void Start () {
    11.         item = new Item ();
    12.         item.type = Item.Type.weapon;
    13.         XmlSerializer serializer = new XmlSerializer (typeof(Item));
    14.         FileStream stream = new FileStream ("/tmp/test.xml", FileMode.Create);
    15.         serializer.Serialize (stream, item);
    16.         stream.Close ();
    17.  
    18.     }
    19. }
    20.  
    21. [System.Serializable]
    22. public class Item {
    23.     public enum Type {normal, food, weapon, shield, armour, fishing_rod, pickaxe};
    24.     public Type type;
    25. }
    26.  
    27.  
    $ cat /tmp/test.xml
    <?xml version="1.0" encoding="us-ascii"?>
    <Item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <type>weapon</type>
    </Item>

    This is why we post all the code needed to repeat your issue and not just a snippet :p
     
  3. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    354
    Ok here's all my serialization code (you have all the item code needed):
    SaveObject:
    Code (CSharp):
    1. [Serializable]
    2. public class SaveObject {
    3.  
    4.     public string name;
    5.     public bool enabled = true;
    6.     public List<SaveComponent> components = new List<SaveComponent>();
    7.     public List<SaveObject> childs = new List<SaveObject>();
    8.  
    9. }
    10.  
    SaveComponent:
    Code (CSharp):
    1. [Serializable]
    2. public class SaveComponent {
    3.  
    4.     public string type;
    5.     public bool enabled = true;
    6.     public List<SerializableDictonary> variables = new List<SerializableDictonary>();
    7.  
    8. }
    9.  
    DataHolder:
    Code (CSharp):
    1. public class DataHolder {
    2.  
    3.     public List<SaveObject> saveObjects = new List<SaveObject>();
    4.  
    5. }
    SerializableDictonary:
    Code (CSharp):
    1. [Serializable]
    2. public class SerializableDictonary {
    3.  
    4.     public string key;
    5.     public object value;
    6.  
    7.     public SerializableDictonary() {
    8.     }
    9.  
    10.     public SerializableDictonary(string key, object value) {
    11.         this.key = key;
    12.         this.value = value;
    13.     }
    14.  
    15. }
    DataController (The most inportant):
    Code (CSharp):
    1. public class DataController : MonoBehaviour {
    2.  
    3.     public static DataHolder dataHolder;
    4.  
    5. public static void saveObject(GameObject gameObject) {
    6.         if (dataHolder == null) {
    7.             dataHolder = new DataHolder ();
    8.         }
    9.  
    10.         SaveObject saveObject = new SaveObject();
    11.  
    12.         addComponenentsToObject (gameObject, saveObject);
    13.  
    14.         for (int i = 0; i < gameObject.transform.childCount; i++) {
    15.             SaveObject child = new SaveObject ();
    16.             saveObject.childs.Add (child);
    17.             addComponenentsToObject (gameObject.transform.GetChild (i).gameObject, child);
    18.             if (gameObject.transform.GetChild(i).childCount > 0) {
    19.                 for (int j = 0; j < gameObject.transform.GetChild(i).childCount; j++) {
    20.                     SaveObject child2 = new SaveObject ();
    21.                     saveObject.childs.Add (child2);
    22.                     addComponenentsToObject (gameObject.transform.GetChild(i).GetChild(j).gameObject, child2);
    23.                 }
    24.             }
    25.         }
    26.      
    27.  
    28.         dataHolder.saveObjects.Add(saveObject);
    29.  
    30.         XmlSerializer serializer = new XmlSerializer (typeof(DataHolder));
    31.         FileStream stream = new FileStream ("Assets/test.sav", FileMode.Create);
    32.         serializer.Serialize (stream, dataHolder);
    33.         stream.Close();
    34.     }
    35.  
    36.     private static void addComponenentsToObject(GameObject gameObject, SaveObject saveObject) {
    37.         Component[] components = gameObject.GetComponents<Component> ();
    38.  
    39.         for (int i = 0; i < components.Length; i++) {
    40.             Component component = components [i];
    41.             SaveComponent saveComponent = new SaveComponent ();
    42.             for (int j = 0; j < component.GetType ().GetFields ().Length; j++) {
    43.                 saveComponent.type = component.ToString ();
    44.                 string fieldName = component.GetType ().GetFields () [j].Name;
    45.                 string componentString = component.GetType ().GetField (fieldName).GetValue (component).ToString ();
    46.                 if (componentString != "null" && !componentString.Contains ("(") && !componentString.Contains("{") && !componentString.Contains ("[")) {
    47.                     saveComponent.variables.Add (new SerializableDictonary (fieldName, component.GetType ().GetField (fieldName).GetValue (component)));
    48.                 }
    49.             }
    50.             if (saveComponent.type != null && saveComponent.variables.Count != 0) {
    51.                 saveObject.components.Add (saveComponent);
    52.             }
    53.         }
    54.     }
    55.  
    56. }
    57.  
    I know a lot of code :/
     
  4. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    Code (csharp):
    1.  
    2.  saveComponent.variables.Add (new SerializableDictonary (fieldName, component.GetType ().GetField (fieldName).GetValue (component)));
    3.  
    You are putting the value in the dictionary not the string value. Value may not be a primitive as your guards aren't strong enough. There is an type.IsPrimitive check although its a little too strong as it doesn't consider string and a few other types we can treat as primitive to be primitives.

    I'd add an extension class like this:

    Code (csharp):
    1.  
    2. public static class TypeExtension {
    3.     public static bool IsSimple(this Type type) {
    4.         return type.IsPrimitive || type.IsEnum || type.Equals(typeof(string));
    5.     }
    6. }
    7.  
    And then in your loop:

    Code (csharp):
    1.  
    2. FieldInfo[] fields = component.GetType ().GetFields ();
    3. for (int j = 0; j < fields.Length; j++) {
    4.   if (fields[j].GetType ().IsSimple ()) {
    5.     // Write to dict
    6.   }
    7. }
    8.  
    This way you guarantee only serializable things get written.
     
    Last edited: Jul 19, 2017
  5. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    354
    I did some changes but for some reason all field's type is System.Reflection.Monofield

    Code (CSharp):
    1. Type fieldType = component.GetType ().GetFields()[j].GetType();
    2.                 print (fieldType);
     
  6. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    354
    Changeing the last getType() to Fieldtype fixed that problem. It still can't serialise the enumeration and gives the same error.
     
  7. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    5,041
    If you want to PM me a link to a project zip I'll take a look, but I don't have time to try to replicate your entire scene manually.
     
  8. MCrafterzz

    MCrafterzz

    Joined:
    Jun 3, 2017
    Posts:
    354
    I did fix it by taking the string value when it comes to enums and taking the normal value when it comes to other object. It's good enough for me so thanks for your help.