第三层级的List<T>对象无法使用Newtonsoft Json.Net反序列化(在文档中找不到解决方案)

4

我有一个项目,需要从Http服务器读取响应。响应以Json格式返回。Json的对象图部分反序列化工作良好,但是最底层的数组失败了,留下了一个空值。

我已经创建了以下代码,可以粘贴到空白测试项目中运行。唯一的测试失败了,我无法弄清原因。示例Json位于顶部的常量字符串中。

我发现来自System.Web.ExtensionsJavaScriptSerializer可以工作(当我使用List而不是数组时)。然而,Json.Net等效物不起作用。在下面的示例中有两个测试,Newtonsoft中的一个失败了,但为什么会失败?我缺少了哪个Newtonsoft文档项?

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Newtonsoft.Json;
using System.Collections.Generic;

/// <summary>
/// Unit Test project that also includes a reference to System.Web.Extensions.
/// Also includes Newtonsoft from NuGet.
/// The constant `_downloadRootObjectEg` holds the sample json.
/// </summary>
namespace Savaged
{
    [TestClass]
    public class DownloadDeserialisationTest
    {
        private const string _downloadRootObjectEg = "{ \"error\": \"\", \"success\": true, \"data\": [{ \"data\": [{ \"TextSearched\": \"New product\", \"TextFound\": \"New product\", \"data \": [{ \"x\": 0.585, \"y\": 0.21496437 }, { \"x\": 0.63666666, \"y\": 0.21496437 }, { \"x\": 0.6933333, \"y\": 0.23515439 } ], \"Page\": 16 }, { \"TextSearched\": \"Expiry\", \"TextFound\": \"Expiry\", \"data \": [{ \"x\": 0.6666667, \"y\": 0.16270784 }, { \"x\": 0.7133333, \"y\": 0.16270784 }, { \"x\": 0.7133333, \"y\": 0.18052256 }, { \"x\": 0.6666667, \"y\": 0.18052256 } ], \"Page\": 39 }, { \"TextSearched\": \"Expiry\", \"TextFound\": \"Expiry\", \"data \": [{ \"x\": 0.47833332, \"y\": 0.6686461 }, { \"x\": 0.52166665, \"y\": 0.6686461 }, { \"x\": 0.52166665, \"y\": 0.6864608 }, { \"x\": 0.47833332, \"y\": 0.6864608 } ], \"Page\": 43 } ], \"context\": { \"FileLocation\": \"Product-09-08-2007.pdf\", \"ID\": 1, \"Type\": \"product\" } }, { \"data\": [{ \"TextSearched\": \"New product\", \"TextFound\": \"New product\", \"data \": [{ \"x\": 0.585, \"y\": 0.21496437 }, { \"x\": 0.63666666, \"y\": 0.21496437 }, { \"x\": 0.6933333, \"y\": 0.23515439 }, { \"x\": 0.6433333, \"y\": 0.23515439 } ], \"Page\": 16 }, { \"TextSearched\": \"Expiry\", \"TextFound\": \"Expiry\", \"data \": [{ \"x\": 0.6666667, \"y\": 0.16270784 }, { \"x\": 0.7133333, \"y\": 0.16270784 }, { \"x\": 0.7133333, \"y\": 0.18052256 }, { \"x\": 0.6666667, \"y\": 0.18052256 } ], \"Page\": 39 } ], \"context\": { \"FileLocation\": \"Product-09-08-2007.pdf\", \"ID\": 1, \"Type\": \"product\" } } ], \"count\": 2 }";

        [TestMethod]
        public void DeserialiseTest()
        {
            var downloadRootObject =
                JsonConvert.DeserializeObject<DownloadRootObject>(_downloadRootObjectEg);

            Assert.IsNotNull(downloadRootObject.Data[0].Data[0].Data, "Why?");
        }

        [TestMethod]
        public void JavaScriptSerializerTest()
        {
            var downloadRootObject = new System.Web.Script.Serialization.
                JavaScriptSerializer().Deserialize<DownloadRootObject>(_downloadRootObjectEg);

            Assert.IsNotNull(downloadRootObject.Data[0].Data[0].Data, "Why?");
        }
    }

    #region Concrete implementation

    public abstract class RootObjectBase
    {
        public string Error { get; set; }

        public bool Success { get; set; }
    }

    public class DownloadRootObject : RootObjectBase
    {
        public DownloadRootObject()
        {
            Data = new List<WordSearch>();
        }

        [JsonConstructor]
        public DownloadRootObject(List<WordSearch> data)
        {
            Data = data;
        }

        public List<WordSearch> Data { get; set; }

        public int Count { get; set; }
    }

    public class WordSearch
    {
        public WordSearch()
        {
            Data = new List<Match>();
        }

        [JsonConstructor]
        public WordSearch(Context context, List<Match> data)
        {
            Context = context;
            Data = data;
        }

        public Context Context { get; set; }

        public List<Match> Data { get; set; }
    }

    public class Context
    {
        public string FileLocation { get; set; }

        public int ID { get; set; }

        public string Type { get; set; }
    }

    public class Match
    {
        public Match()
        {
            Data = new List<PointF>();
        }

        [JsonConstructor]
        public Match(List<PointF> data)
        {
            Data = data;
        }

        public int Page { get; set; }

        // TODO switch this to System.Drawing.PointF
        public List<PointF> Data { get; set; }

        public string TextSearched { get; set; }

        public string TextFound { get; set; }
    }

    public class PointF
    {
        public float X { get; set; }

        public float Y { get; set; }
    }

    #endregion
}

非常感激您的帮助!


糟糕!System.Web.Script.Serialization.JavaScriptSerializer运行良好。 我添加了引用并通过以下测试: ` [TestMethod] public void JavaScriptSerializerTest() { var downloadRootObject = new System.Web.Script.Serialization. JavaScriptSerializer().Deserialize<DownloadRootObject>(_downloadRootObjectEg); Assert.IsNotNull(downloadRootObject.Data[0].Data[0].Data, "为什么?"); }` - David Savage
更正之前的评论!我还将所有数组更改为List<T>。 - David Savage
@Icepickle 样例在提供的代码中。它是顶部的字符串。 - David Savage
@Icepickle 我使用Notepad++和它的Json插件来格式化一行代码,以便我可以正确地查看它。(当然,首先必须删除转义引号的反斜杠)。 - David Savage
@DavidSavage:在评论中发布代码并不特别易读。 - Flater
显示剩余2条评论
2个回答

2
据我所见,提到的列表没有被反序列化,因为最低级别的"data"属性中有一个尾随空格。
 \"data \": [{ \"x\": 0.585, \"y\": 0.21496437 }

但实际上应该是这样的:
 \"data\": [{ \"x\": 0.585, \"y\": 0.21496437 }

2
@Redstone基本上给出了正确的答案(已点赞)。在JSON中,您的最内层数组键称为"data "(带有尾随空格),而不是"data"。因此,实际上内部列表没有得到反序列化,因为序列化程序无法将JSON中的键与您的Match类中的Data属性匹配。
至于为什么“JavaScriptSerializer”与“Json.Net”中的结果不同--其实并不是这样的。您的测试并不完全等价。区别在于每种情况下您使用了不同的构造函数。JavaScriptSerializer不支持[JsonConstructor]属性,因此它始终调用默认构造函数,在您的代码中创建一个空列表。Json.Net调用您标记的另一个构造函数,该构造函数不会创建空列表。由于反序列化程序无法找到该构造函数中data参数的匹配项,因此它传递了一个null值。在您的测试中,您只测试生成的列表是否为空,而不是它是否成功检索任何值。如果扩展测试,您将看到使用JavaScriptSerializer获得空列表而不是来自JSON的实际数据点。
最好的解决方案是修复您的JSON,使其具有正确的data键,而不带有尾随空格。如果您无法这样做(例如,因为您不拥有JSON),则下一个最佳选项是使用[JsonProperty("data ")]标记Match类中的Data属性。Json.Net仍将在构造函数中将null传递给data参数(毕竟,参数名称不能包含空格),但它应该会找到并使用公共属性访问器正确设置列表。请注意,此解决方案对于JavaScriptSerializer不起作用,因为它也不支持[JsonProperty]属性,因此如果您需要使用该序列化程序,则可能必须编写自定义转换器以解决此问题。Json.Net还支持自定义转换器,因此如果您需要确保使用非null数据参数调用备用构造函数,则这是另一种选择。有关该方法的更多信息,请参见JSON.net:如何在不使用默认构造函数的情况下反序列化?

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接