首页 > 解决方案 > 如果不存在,则序列化并跳过

问题描述

我将 Unity 与 GameSparks 一起使用,并且有一个序列化程序脚本,它将 GameSparks 数据对象转换为 ac# 模型。脚本如下所示:

public static object GSDataToObject(GSData gsData)
    {
        //Debug.Log("GSSerializer Return: \n"+gsData.JSON);

        Type objType = Type.GetType(gsData.GetString("type"));

        object obj = Activator.CreateInstance(objType);

        foreach(var typeField in objType.GetFields())
        {
            if(!typeField.IsNotSerialized)
            {
                if(typeField.FieldType == typeof(string))
                {
                    typeField.SetValue(obj, gsData.GetString(typeField.Name));
                }
                else if(typeField.FieldType == typeof(int))
                {
                    typeField.SetValue(obj, (int)gsData.GetNumber(typeField.Name).Value);    
                }
                else if(typeField.FieldType == typeof(float))
                {
                    typeField.SetValue(obj, (float)gsData.GetFloat(typeField.Name).Value);    
                }
                else if(typeField.FieldType == typeof(bool))
                {
                    typeField.SetValue(obj, gsData.GetBoolean(typeField.Name));    
                }
                else if(typeField.FieldType == typeof(DateTime))
                {
                    typeField.SetValue(obj, gsData.GetDate(typeField.Name));    
                }
                else if((typeField.FieldType == typeof(List<string>) || typeField.FieldType == typeof(string[]) ))
                {
                    typeField.SetValue(obj, (typeField.FieldType == typeof(List<string>)) ? (object)gsData.GetStringList(typeField.Name) : gsData.GetStringList(typeField.Name).ToArray());  
                }
                else if((typeField.FieldType == typeof(List<int>) || typeField.FieldType == typeof(int[])) )
                {
                    typeField.SetValue(obj, (typeField.FieldType == typeof(List<int>)) ? (object)gsData.GetIntList(typeField.Name) : gsData.GetIntList(typeField.Name).ToArray());    
                }
                else if((typeField.FieldType == typeof(List<float>) || typeField.FieldType == typeof(float[])) )
                {
                    typeField.SetValue(obj, (typeField.FieldType == typeof(List<float>)) ? (object)gsData.GetFloatList(typeField.Name) : gsData.GetFloatList(typeField.Name).ToArray());    
                }

                else if(typeField.FieldType.IsClass && !typeField.FieldType.IsGenericType && !typeField.FieldType.IsArray)
                {
                    typeField.SetValue(obj, GSDataToObject(gsData.GetGSData(typeField.Name)));
                }
                else if(!typeField.FieldType.IsArray && typeof(IList).IsAssignableFrom(typeField.FieldType))
                {
                    IList genericList = Activator.CreateInstance(typeField.FieldType) as IList;
                    foreach(GSData gsDataElem in gsData.GetGSDataList(typeField.Name))
                    {
                        object elem = GSDataToObject(gsDataElem);
                        genericList.Add(elem);
                    }
                    typeField.SetValue(obj, genericList);
                }
                else if(typeField.FieldType.IsArray)
                {
                    List<GSData> gsArrayData = gsData.GetGSDataList(typeField.Name);
                    // create a new instance of the array. The Activator class cannot do this for arrays //
                    // so this will create a new array of types inside the array, with the count of what is in the gsdata list //
                    Array newArray = Array.CreateInstance(typeField.FieldType.GetElementType(), gsArrayData.Count);
                    object[] objArray = new object[gsArrayData.Count]; // create a new array of objects where the serialized objects will be kept
                    for(int i = 0; i < gsArrayData.Count; i++)
                    {
                        objArray[i] = GSDataToObject(gsArrayData[i]); // convert the JSON data inside the list to an object
                    }
                    Array.Copy(objArray, newArray, objArray.Length); //covert the object[] to the original type
                    typeField.SetValue(obj, newArray);
                }

            }
        }

        return obj;
}  

我的模型是这样的:

[System.Serializable]
public class UserData
{
    public string name;
    public string email;
}

然后当我从 GameSparks 获取数据时调用脚本,如下所示:

UserData uData = new UserData();
uData = GameSparksSerialiser.GSDataToObject(response.ScriptData.GetGSData("@userReturn")) as UserData;

现在,这一切都很完美,但是如果我决定从现在开始在所有新创建的文档(mongodb)中添加一个新字段,我就有问题了。我在 Unity 中将新字段添加到我的模型中,如下所示:

[System.Serializable]
 public class UserData
 {
     public string name;
     public string email;
     public bool isAdmin;
 }

这对所有新文档都没有错误,但是在尝试序列化旧文档时,我收到此错误:

System.InvalidOperationException: Nullable object must have a value.

现在,我怎样才能避免这种情况,使用序列化程序脚本......是否可以不犯错误,所以我不必更改所有旧文档?

希望提前帮助和感谢:-)

标签: c#unity3dserializationgamesparks

解决方案


不知道GSDatagetter 是什么或如何实现的,但问题似乎来自例如(如果您包含完整的错误消息会有所帮助)

typeField.SetValue(obj, gsData.GetBoolean(typeField.Name));

您正在尝试获取以前不存在的字段的值。

您应该将其包装在一个try-catch块中。为了不必为每种情况都这样做,您可能应该包装整个

foreach(var typeField in objType.GetFields())
{
    try{        
        if(!typeField.IsNotSerialized)
        {
            .......
        }
    }
    catch(Exception e)
    {
        Debug.LogWarning($"{e.GetType} while trying to get value for {typeField.Name}: {e.Message}\n{e.StackTrace}");
    }
}

这仍然会在控制台中引发错误,但不像异常通常会破坏您的应用程序 / for 循环,而是简单地继续其余部分。


一般旁注:

这样做是很多余的

UserData uData = new UserData();

在使用之前

uData = GameSparksSerialiser.GSDataToObject(response.ScriptData.GetGSData("@userReturn")) as UserData;

这只会为 GarbageCollector 创建一些工作,用于销毁这个未使用的UserDatacreate by实例new。只需直接使用

UserData uData = GameSparksSerialiser.GSDataToObject(response.ScriptData.GetGSData("@userReturn")) as UserData;

推荐阅读