将InvalidCastException JToken转换为<class> JSON.net

时间:2018-10-19 18:44:50

标签: c# json json.net

TL; DR:我正在尝试创建一个用于保存嵌套JSON数据的类。  我最终解决了自己的问题,但是@dbc很有帮助,他们提供了一个解决方案,如果您想以自己的方式实现,它可能会更快。我已经完整记录了我的解决方案和示例用法,并在下面将其标记为已回答。


我正在创建一个项目,打算在其中存储大量嵌套的JSON数据。

我想创建一个简单的“动态对象”,而不是创建一百个类,每个类都有各自的变量/属性,然后每次我想更改某些内容时都必须对其进行修改。

此对象保存所有数据以及所有子级数据的根。在JSON中,这表示为:

{
    "name":"foo",
    "id":0,
    "attributes":
    {
        "slippery":true,
        "dangerous":true
    },
    "costs":
    {
        "move":1,
        "place":2,
        "destroy":3
    }
}

其中的根结构包含数据“名称”和“ id”,以及包含各自数据的子级“属性”和“成本”。

我正在为此使用json.net库,而我当前的类如下:

public class Data : JObject
{
    public void CreateChildUnderParent(string parent, string child)
    {
        Data obj = GetValueOfKey<Data>(parent);

        if(obj != null)
            obj.CreateChild(child);
    }

    public void CreateChild(string child)
    {
        AddKey(child, new Data());
    }

    public void AddKeyToParent(string parent, string key, JToken value)
    {
        Data parentObject = GetValueOfKey<Data>(parent);

        if(parentObject != null)
            parentObject.AddKey(key, value);
    }

    public void AddKey(string key, JToken value)
    {
        Add(key, value);
    }

    public void RemoveKeyFromParent(string parent, string key)
    {
        Data parentObject = GetValueOfKey<Data>(parent);

        if(parentObject != null)
            parentObject.RemoveKey(key);
    }

    public void RemoveKey(string key)
    {
        Remove(key);
    }

    public T GetValueFromParent<T>(string parent, string key)
    {
        Data parentObject = GetValueOfKey<Data>(parent);
        if(parentObject != null)
            return parentObject.GetValue(key).ToObject<T>();

        return default;
    }

    public T GetValueOfKey<T>(string key)
    {
        foreach (var kvp in this)
            if (kvp.Value is Data)
            {
                T value = ((Data)kvp.Value).GetValueOfKey<T>(key);
                if (value != null)
                    return value;
            }

        JToken token = GetValue(key);
        if(token != null)
            return token.ToObject<T>();  //throws exception

        return default;
    }
}

我可以添加孩子,但是当我尝试访问他们时出现了问题。我的

内抛出了InvalidCastException
public T GetValueOfKey<T>(string key)

每当我使用

调用该方法时
Data 

作为通用类型。

例如:

Data data = GetValueOfKey<Data>("attributes");

引发异常。我不确定为什么会这样,所以任何帮助将不胜感激!

编辑:

这是抛出完整的错误日志:

InvalidCastException: Specified cast is not valid.
(wrapper castclass) System.Object.__castclass_with_cache(object,intptr,intptr)
Newtonsoft.Json.Linq.JToken.ToObject[T] () (at <97722d3abc9f4cf69f9e21e6770081b3>:0)
Data.GetValueOfKey[T] (System.String key) (at Assets/Scripts/Attributes/Object/Data.cs:74)
Data.AddKeyToParent (System.String parent, System.String key, Newtonsoft.Json.Linq.JToken value) (at Assets/Scripts/Attributes/Object/Data.cs:23)
DataController.Awake () (at Assets/Scripts/Controllers/DataController.cs:35)

以及导致该异常的实例化示例:

public class DataController
{
    void Awake()
    {
        Data data = new Data();
        data.AddKey("name", "foo");
        data.CreateChild("attributes");
        data.AddKeyToParent("attributes", "slippery", true); //throws exception (line 35)
    }
}

更新(10/20/18):

好吧,所以今天下午我遍历了代码,并将其重写为包装类,现在,根JObject存储在我的Data中的变量中,访问器方法调整其属性。

但是,我遇到了一个问题。这是更新后的类(最小化为问题):

public class Data 
{
    public JObject data;

    public Data()
    {
        data = new JObject();
    }

    public void AddChild(string child)
    {
        data.Add(child, new JObject());
    }

    public void AddKeyWithValueToParent(string parent, string key, JToken value)
    {
        JObject parentObject = GetValueOfKey<JObject>(parent);

        if(parentObject != null)
            parentObject.Add(key, value);
    }

    public void AddKeyWithValue(string key, JToken value)
    {
        data.Add(key, value);
    }

    public T GetValueOfKey<T>(string key)
    {
        return GetValueOfKey<T>(key, data);
    }

    private T GetValueOfKey<T>(string key, JObject index)
    {
        foreach (var kvp in index)
            if (kvp.Value is JObject)
            {
                T value = GetValueOfKey<T>(key, kvp.Value.ToObject<JObject>());
                if (value != null)
                    return value;
            }

        JToken token = index.GetValue(key);
        if (token != null)
            return token.ToObject<T>();

        return default;
    }
}

下面是一个如何构造Data对象并使用其方法的示例:

public class DataController
{
    void Awake() {
        Data data = new Data();
        data.AddKeyWithValue("name", "foo");
        data.AddChild("attributes");
        data.AddKeyWithValueToParent("attributes", "slippery", true);
    }
}

因此,添加键值对以及创建子代的一切工作都非常好!完全没有InvalidCastException,是的!但是,当我尝试通过JsonConvert.SerializeObject(data)序列化对象时,它不能完全序列化它。

我将程序输出到控制台以显示序列化,它看起来像这样:

{"data":{"name":"foo","attributes":{}}}

我已经进行了检查,以确保当我调用data.AddKeyWithValueToParent("attributes", "slippery", true)时,确实确实找到了带有键JObject的{​​{1}}值,甚至似乎可以成功添加新的键-下的值对attributes。但是出于某种原因,序列化根对象"slippery":true似乎无法识别data对象中是否存在任何内容。有想法吗?

我认为可能正在发生的事情是,从attributes返回的值不充当参考对象,而是一个全新的对象,因此对该对象所做的更改不会反映在原始对象中。

1 个答案:

答案 0 :(得分:0)

我知道了!我是对的,我的GetValueOfKey方法返回的值返回的是一个全新的对象,而不是对它找到的实例的引用。查看我的代码,这应该已经很明显了,但是我很累,我希望一切都变得容易哈哈。

无论如何,对于任何有相同问题,并且只是在寻找一种简单的方法来使用Json.NET库存储和读取嵌套的键-值对的人,下面是完成此操作的类(也使用JsonConvert可序列化和反序列化):

public class Data 
{
    [JsonProperty]
    private JObject data;

    public Data()
    {
        data = new JObject();
    }

    public void AddChildUnderParent(string parent, string child)
    {
        JObject parentObject = GetValueOfKey<JObject>(parent);

        if (parentObject != null)
        {
            parentObject.Add(child, new JObject());
            ReplaceObject(parent, parentObject);
        }
    }

    public void AddChild(string child)
    {
        data.Add(child, new JObject());
    }

    public void AddKeyWithValueToParent(string parent, string key, JToken value)
    {
        JObject parentObject = GetValueOfKey<JObject>(parent);

        if(parentObject != null)
        {
            parentObject.Add(key, value);
            ReplaceObject(parent, parentObject);
        }
    }

    public void AddKeyWithValue(string key, JToken value)
    {
        data.Add(key, value);
    }

    public void RemoveKeyFromParent(string parent, string key)
    {
        JObject parentObject = GetValueOfKey<JObject>(parent);

        if (parentObject != null)
        {
            parentObject.Remove(key);
            ReplaceObject(parent, parentObject);
        }
    }

    public void RemoveKey(string key)
    {
        data.Remove(key);
    }

    public T GetValueFromParent<T>(string parent, string key)
    {
        JObject parentObject = GetValueOfKey<JObject>(parent);

        if (parentObject != null)
            return parentObject.GetValue(key).ToObject<T>();

        return default;
    }

    public T GetValueOfKey<T>(string key)
    {
        return GetValueOfKey<T>(key, data);
    }

    private T GetValueOfKey<T>(string key, JObject index)
    {
        foreach (var kvp in index)
            if (kvp.Value is JObject)
            {
                T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
                if (value != null)
                    return value;
            }

        JToken token = index.GetValue(key);
        if (token != null)
        {
            data = token.Root.ToObject<JObject>();
            return token.ToObject<T>();
        }

        return default;
    }

    public void ReplaceObject(string key, JObject replacement)
    {
        ReplaceObject(key, data, replacement);
    }

    private void ReplaceObject(string key, JObject index, JObject replacement)
    {
        foreach (var kvp in index)
            if (kvp.Value is JObject)
                ReplaceObject(key, (JObject)kvp.Value, replacement);

        JToken token = index.GetValue(key);
        if (token != null)
        {
            JToken root = token.Root;

            token.Replace(replacement);
            data = (JObject)root;
        }
    }
}

这应该使任何人都有一个良好的开端。我计划在某些地方使用params修饰符更新代码,以允许多次调用,但是现在我很高兴自己能正常工作。您会注意到我不得不创建一个ReplaceObject方法,因为没有它,原始的private JObject data从未真正更新过,无法说明对GetValueOfKey返回的变量所做的更改。 / p>

无论如何,非常感谢@dbc在这整个过程中提供的所有帮助,我希望这篇文章对以后的人有所帮助!

-ShermanZero

编辑:

因此,我花了更多的时间来开发该类,并且我认为我将它固定在一个通用点上,任何人都可以简单地将其复制粘贴并轻松地实现到自己的程序中。虽然,我个人认为@dbc如果您关心速度的纳秒级至毫秒级差异,则可以提供更快的解决方案。不过,就我个人而言,我认为这不会有太大的不同。

这是我的完整实现,包括文档和错误记录:

public class Data 
{
    [JsonExtensionData]
    private JObject root;

    private Texture2D texture;

    private char delimiter = ',';

    /// <summary>
    /// Creates a new Data class with the default delimiter.
    /// </summary>
    public Data()
    {
        root = new JObject();
    }

    /// <summary>
    /// Creates a new Data class with a specified delimiter.
    /// </summary>
    /// <param name="delimiter"></param>
    public Data(char delimiter) : this()
    {
        this.delimiter = delimiter;
    }

    /// <summary>
    /// Adds a child node to the specified parent(s) structure, which is split by the delimiter, with the specified name.
    /// </summary>
    /// <param name="name"></param>
    /// <param name="parents"></param>
    public void AddChild(string name, string parents)
    {
        AddChild(name, parents.Split(delimiter));
    }

    /// <summary>
    /// Adds a child node to the specified parent(s) structure with the specified name.
    /// </summary>
    /// <param name="name"></param>
    /// <param name="parents"></param>
    public void AddChild(string name, params string[] parents)
    {
        string lastParent;
        JObject parentObject = ReturnParentObject(out lastParent, parents);

        if (parentObject != null)
        {
            parentObject.Add(name, new JObject());
            ReplaceObject(lastParent, parentObject, parents);
        } else
        {
            string message = "";
            foreach (string parent in parents)
                message += parent + " -> ";

            throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
        }
    }

    /// <summary>
    /// Adds a child node to the root structure with the specified name.
    /// </summary>
    /// <param name="name"></param>
    public void AddChild(string name)
    {
        root.Add(name, new JObject());
    }

    /// <summary>
    /// Adds the specified key-value pair to the specified parent(s) structure, which is split by the delimiter.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="parents"></param>
    public void AddKeyWithValue(string key, JToken value, string parents)
    {
        AddKeyWithValue(key, value, parents.Split(delimiter));
    }

    /// <summary>
    /// Adds the specified key-value pair to the specified parent(s) structure.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    /// <param name="parents"></param>
    public void AddKeyWithValue(string key, JToken value, params string[] parents)
    {
        string lastParent;
        JObject parentObject = ReturnParentObject(out lastParent, parents);

        if (parentObject != null)
        {
            parentObject.Add(key, value);
            ReplaceObject(lastParent, parentObject, parents);
        } else
        {
            string message = "";
            foreach (string parent in parents)
                message += parent + " -> ";

            throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
        }
    }

    /// <summary>
    /// Adds the specified key-value pair to the root structure.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public void AddKeyWithValue(string key, JToken value)
    {
        root.Add(key, value);
    }

    /// <summary>
    /// Removes the specified key from the specified parent(s) structure, which is split by the delimiter.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    public void RemoveKey(string key, string parents)
    {
        RemoveKey(key, parents.Split(delimiter));
    }

    /// <summary>
    /// Removes the specified key from the specified parent(s) structure.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    public void RemoveKey(string key, params string[] parents)
    {
        string lastParent;
        JObject parentObject = ReturnParentObject(out lastParent, parents);

        if (parentObject != null)
        {
            parentObject.Remove(key);
            ReplaceObject(lastParent, parentObject, parents);
        } else
        {
            string message = "";
            foreach (string parent in parents)
                message += parent + " -> ";

            throw new ParentNotFoundException($"The parent '{ message.Substring(0, message.LastIndexOf("->")) }' was not found.");
        }
    }

    /// <summary>
    /// Removes the specified key from the root structure.
    /// </summary>
    /// <param name="key"></param>
    public void RemoveKey(string key)
    {
        root.Remove(key);
    }

    /// <summary>
    /// Returns if the specified key is contained within the parent(s) structure, which is split by the delimiter.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    /// <returns></returns>
    public bool HasValue(string key, string parents)
    {
        return HasValue(key, parents.Split(delimiter));
    }

    /// <summary>
    /// Returns if the specified key is contained within the parent(s) structure.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    /// <returns></returns>
    public bool HasValue(string key, params string[] parents)
    {
        //string lastParent = parents[parents.Length - 1];
        //Array.Resize(ref parents, parents.Length - 1);
        string lastParent;
        JObject parentObject = ReturnParentObject(out lastParent, parents);

        if (parentObject == null)
            return false;
        else if (parentObject == root && parents.Length > 0)
            return false;

        IDictionary<string, JToken> dictionary = parentObject;
        return dictionary.ContainsKey(key);
    }

    /// <summary>
    /// Returns the deepest parent object referenced by the parent(s).
    /// </summary>
    /// <param name="lastParent"></param>
    /// <param name="parents"></param>
    /// <returns></returns>
    private JObject ReturnParentObject(out string lastParent, string[] parents)
    {
        lastParent = null;
        if(parents.Length > 0)
        {
            lastParent = parents[parents.Length - 1];
            Array.Resize(ref parents, parents.Length - 1);

            return GetValueOfKey<JObject>(lastParent, parents);
        }

        return root;
    }

    /// <summary>
    /// Returns the value of the specified key from the specified parent(s) structure, which is split by the delimiter.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    /// <returns></returns>
    public T GetValueOfKey<T>(string key, string parents)
    {
        return GetValueOfKey<T>(key, parents.Split(delimiter));
    }

    /// <summary>
    /// Returns the value of the specified key from the specified parent(s) structure.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="parents"></param>
    /// <returns></returns>
    public T GetValueOfKey<T>(string key, params string[] parents)
    {
        JObject parentObject = null;
        for(int i = 0; i < parents.Length; i++)
            parentObject = GetValueOfKey<JObject>(parents[i].Trim(), parentObject == null ? root : parentObject);

        return GetValueOfKey<T>(key, parentObject == null ? root : parentObject);
    }

    /// <summary>
    /// Returns the value of the specified key from the root structure.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <returns></returns>
    public T GetValueOfKey<T>(string key)
    {
        return GetValueOfKey<T>(key, root);
    }

    /// <summary>
    /// Returns the value of the specified key from a given index in the structure.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="key"></param>
    /// <param name="index"></param>
    /// <returns></returns>
    private T GetValueOfKey<T>(string key, JObject index)
    {
        JToken token = index.GetValue(key);
        if (token != null)
            return token.ToObject<T>();

        foreach (var kvp in index)
            if (kvp.Value is JObject)
            {
                T value = GetValueOfKey<T>(key, (JObject)kvp.Value);
                if (value != null)
                    return value;
            }

        return default(T);
    }

    /// <summary>
    /// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s), which is split by the delimiter.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="replacement"></param>
    /// <param name="parents"></param>
    public void ReplaceObject(string key, JObject replacement, string parents)
    {
        ReplaceObject(key, root, replacement, parents.Split(delimiter));
    }

    /// <summary>
    /// Replaces an object specified by the given key and ensures object is replaced within the correct parent(s).
    /// </summary>
    /// <param name="key"></param>
    /// <param name="replacement"></param>
    /// <param name="parents"></param>
    public void ReplaceObject(string key, JObject replacement, params string[] parents)
    {
        ReplaceObject(key, root, replacement, parents);
    }

    /// <summary>
    /// Replaces an object specified by the given key.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="replacement"></param>
    public void ReplaceObject(string key, JObject replacement)
    {
        ReplaceObject(key, root, replacement);
    }

    /// <summary>
    /// Replaces an object specified by the given key within the structure and updates changes to the root node.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="index"></param>
    /// <param name="replacement"></param>
    private void ReplaceObject(string key, JObject index, JObject replacement)
    {
        foreach (var kvp in index)
            if (kvp.Value is JObject)
                ReplaceObject(key, (JObject)kvp.Value, replacement);

        JToken token = index.GetValue(key);
        if (token != null)
        {
            JToken root = token.Root;

            token.Replace(replacement);
            this.root = (JObject)root;
        }
    }

    /// <summary>
    /// Replaces an object specified by the given key within the structure, ensuring object is replaced within the correct parent, and updates changes to the root node.
    /// </summary>
    /// <param name="key"></param>
    /// <param name="index"></param>
    /// <param name="replacement"></param>
    /// <param name="parents"></param>
    private void ReplaceObject(string key, JObject index, JObject replacement, params string[] parents)
    {
        foreach (var kvp in index)
            if (kvp.Value is JObject)
            {
                bool valid = false;
                foreach (string str in parents)
                    if (str.Trim() == kvp.Key)
                        valid = true;

                if(valid)
                    ReplaceObject(key, (JObject)kvp.Value, replacement);
            }

        JToken token = index.GetValue(key);
        if (token != null)
        {
            JToken root = token.Root;

            token.Replace(replacement);
            this.root = (JObject)root;
        }
    }

    /// <summary>
    /// Returns the root structure as JSON.
    /// </summary>
    /// <returns></returns>
    public override string ToString()
    {
        return root.ToString();
    }

    /// <summary>
    /// A ParentNotFoundException details that the supplied parent was not found within the structure.
    /// </summary>
    private class ParentNotFoundException : Exception
    {
        public ParentNotFoundException() { }

        public ParentNotFoundException(string message) : base(message) { }

        public ParentNotFoundException(string message, Exception inner) : base(message, inner) { }
    }
}

用法示例:

Data data = new Data();

data.AddKeyWithValue("name", "foo");
data.AddChild("costs");
data.AddChild("attributes");

data.AddKeyWithValue("move", 1, "costs");
data.AddKeyWithValue("place", 2, "costs");
data.AddKeyWithValue("destroy", 3, "costs");

data.AddChild("movement", "costs");
data.AddKeyWithValue("slippery", false, "costs", "movement");

data.AddChild("movement", "attributes");
data.AddKeyWithValue("slippery", true, "attributes", "movement");

if(data.HasValue("move", "costs")) {
    Debug.Log(data.GetValueOfKey<int>("move", "costs")
    Debug.Log(data);
}

及其输出:

1
{
 "name": "foo",
  "costs": {
    "move": 1,
    "place": 2,
    "destroy": 3,
    "movement": {
      "slippery": false
    }
  },
  "attributes": {
    "movement": {
      "slippery": true
    }
  }
}