【问题标题】:Deserialize Dynamic JSON file C# NewtonSoft.JSON反序列化动态 JSON 文件 C# NewtonSoft.JSON
【发布时间】:2014-09-17 17:52:55
【问题描述】:

正在反序列化一个动态 JSON 文件,该文件可能包含 2 个单独的类,我不知道数组中将包含哪种类型的数据。

问题是,我将根对象反序列化为“Base”类型,“subtests”对象被反序列化为“Subtest”,但“subtests”数组可能是“Base”类型或“Subtest”类型。

问题: 我将如何以编程方式确定对象是否包含“subtest”,我反序列化为 Base,如果不包含,它应该反序列化为“Subtest”?

我非常感谢在这方面的帮助,因为我的时间很短。

(编辑:添加 cmets 以显示每个对象应反序列化的类型) 这是一个示例(JSON 数据):

{
// Deserializes to type "Base"
  "host": "123456",
  "last_time": "2014-09-15 07:04:49.205000",
  "name": "myName",
  "result": "FAIL",
  "serial": "12345",
  "start_time": "2014-09-15 06:53:36.976000",
// Deserializes to type "List<Subtest>"
  "subtests": [
    {
      "data": {
        "moredata": {
          "ver": "123",
          "real": 123
        }
      },
      "description": "Description of Data",
      "host": "123456",
      "last_time": "2014-09-15 20:32:31.095000",
      "name": "testname.py",
      "result": "PASS",
      "start_time": "2014-09-15 20:32:25.873000",
      "version": "2.014.09.15"
    },
    {
// Nested within Subtest Array, Should deserialize to type "Base" again
      "host": "123456",
      "last_time": "2014-09-15 07:04:49.205000",
      "name": "name of test suite",
      "result": "FAIL",
      "start_time": "2014-09-15 06:53:36.976000",
//Should deserialize to type "List<Subtest>"
      "subtests": [
        {
          "description": "Description of Data",
          "host": "123456",
          "last_time": "2014-09-15 06:53:40.440000",
          "name": "testname.py",
          "result": "FAIL",
          "start_time": "2014-09-15 06:53:36.976000",
          "version": "2.014.09.15"
        },
        {
          "description": "Test Description",
          "host": "123456",
          "last_time": "2014-09-15 06:54:34.905000",
          "name": "testname.py",
          "result": "PASS",
          "start_time": "2014-09-15 06:54:34.827000",
          "version": "2.014.09.15"
        },
        {
          "host": "123456",
          "last_time": "2014-09-15 06:55:01.156000",
          "name": "testname.py",
          "result": "FAIL",
          "start_time": "2014-09-15 06:55:01.156000",
          "version": "2.014.09.15"
        },

      ],
      "version": "1.45"
    }
  ],
  "version": "1.23"
}

示例(代码):

public class Base{
    public string host { get; set; }
    public DateTime last_time { get; set; }
    public string name { get; set; }
    public string result { get; set; }
    public string serial { get; set; }
    public DateTime start_time { get; set; }
    public List<Subtest> subtests { get; set; }
    public string version { get; set; }
}

public class Subtest {
    [JsonProperty("data")]
    public JObject Data { get; set; } // CHECK

    [JsonProperty("description")]
    public string Description { get; set; } // CHECK

    [JsonProperty("host")]
    public string Host { get; set; }

    [JsonProperty("info")]
    public List<StatusDetails> Info { get; set; }

    [JsonProperty("last_time")]
    public DateTime LastRunTime { get; set; }

    [JsonProperty("name")]
    public string TestName { get; set; }

    [JsonProperty("result")]
    public string SubtestRunResult { get; set; }

    [JsonProperty("start_time")]
    public DateTime StartTime { get; set; }

    [JsonProperty("trace")]
    public List<object> Trace { get; set; }

    [JsonProperty("version")]
    public string Version { get; set; }
}

【问题讨论】:

  • JSON 比 XML 轻量级的原因是它省略了对象类型(即 XML 中的标记元素)。我不知道如何为这个确切的答案提供帮助,但这可能是您想要考虑更完整且更复杂的序列化方法的罕见场景之一。
  • BaseSubtest 是否共享一个共同的基本类型?否则你会得到一个List&lt;object&gt;,这不会很有帮助。
  • 我将整个文件反序列化为 Base 类型,其中 subtests 对象反序列化为 List。其中,数组可以再次包含“Base”或“Subtest”。我只需要找出一种方法来确定是否反序列化到基类(如果它包含测试套件)或 Subtest(如果它是子测试)。我不控制传入的数据,所以无法序列化。
  • 正确,所以如果您的列表可以包含两种类型(SubtestBase),则该属性不能是 List&lt;Subtest&gt;。它必须是 List&lt;object&gt;List&lt;SomeBaseTypeThatBothInheritFrom&gt; 才能包含这两种类型。
  • 这很好,但是一旦我将对象列表反序列化为对象,我如何确定它应该反序列化为“Base”或“Subtest”?只需在对象上运行 foreach 并检查“子测试”是否是键?

标签: c# json json.net deserialization


【解决方案1】:

我会重新设计您的类以形成层次结构。我可能在这里错过了一个属性,但你明白了。重要的是转换器。

public abstract class TestBase
{
    public string Host { get; set; }

    [JsonProperty("last_time")]
    public DateTime LastTime { get; set; }

    public string Name { get; set; }
    public string Result { get; set; }

    [JsonProperty("start_time")]
    public DateTime StartTime { get; set; }
    public string Version { get; set; }
}

public class TestSuite : TestBase
{
    public string Serial { get; set; }
    public List<TestBase> Subtests { get; set; }
}

public class Subtest : TestBase
{
    public JObject Data { get; set; }

    public string Description { get; set; }
}

然后,你需要一个自定义转换器来根据subtests属性的存在选择正确的类型:

public class TestBaseConverter : JsonConverter
{
    public override object ReadJson(
        JsonReader reader,
        Type objectType,
        object existingValue,
        JsonSerializer serializer)
    {
        JObject obj = serializer.Deserialize<JObject>(reader);

        TestBase result = null;

        if (obj["subtests"] != null)
        {
            result = new TestSuite();
        }
        else 
        {
            result = new Subtest();
        }

        serializer.Populate(obj.CreateReader(), result);

        return result;
    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(TestBase).IsAssignableFrom(objectType);
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void WriteJson(
        JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotSupportedException();
    }
}

然后你会像这样使用它:

TestSuite suite = JsonConvert.DeserializeObject<TestSuite>(
    json, new TestBaseConverter());

【讨论】:

  • 在我输入答案时没有看到这个。让我试一试,因为它似乎是一个更好的解决方案。谢谢安德鲁!
  • @LeviFuller:当然,如果您有任何问题,请告诉我。
  • 对您的代码印象深刻。试了一下,它一直迭代到最后一个Subtest,但是当它试图退出时(在最后的第 24 次迭代中调用Populate()),它崩溃了。此时,结果是TestSuite,但它认为它需要是Subtest。有任何想法吗? snag.gy/pouum.jpg
  • 它是否成功反序列化任何其他TestSuite 对象? TestSuiteSubtest 有像上面这样的通用基类吗?这个错误让我觉得你有一个List&lt;Subtest&gt; 而不是List&lt;TestBase&gt;
  • 安德鲁,我敬畏地向你鞠躬。希望有一天能达到你的水平。您是 100% 正确的,忘记将类型从 Subtest 更改为 TestBase.... +10000 道具给您。
【解决方案2】:

我最终做的是向我的Subtest 类添加一个List&lt;Subtest&gt; 属性,并在递归foreach 循环函数中检查空值。不像我希望的那样漂亮,但比单独解析和反序列化每个 subtest 对象要好。

private static void GetSubtest(List<Subtest> subtestList) {
        foreach (var subtest in subtestList) {
            if (subtest.Subtests != null) {
                GetSubtest(subtest.Subtests);
            }
            else {
                // add data to Vertica cluster
            }
        }
    }

漫长的一天,非常感谢你们所有试图提供帮助的人。 JSON 的新手,所以我无法理解它。希望这可以帮助其他人在未来。如果您需要更多解释,请在此处发表评论。

【讨论】:

  • 检查不同对象类型的 null 是相当标准的,所以我认为看到您如何处理它,任何人都不会感到震惊。几周前我自己也发现了同样的事情!如果您无法控制数据模型,情况会更糟。
猜你喜欢
  • 1970-01-01
  • 2020-06-07
  • 1970-01-01
  • 2022-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多