反序列化动态JSON文件 C# NewtonSoft.JSON

3

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

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

问题: 如何通过编程确定如果对象包含“subtest”,那么我应该反序列化为Base,如果不包含,则应该反序列化为“Subtest”?

由于时间紧迫,我非常感谢您的帮助。

(编辑:添加了注释以显示每个对象应反序列化到什么类型) 以下是示例(JSON DATA):

{
// 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; }
}

BaseSubtest是否共享一个公共基础类型?否则,你将得到一个List<object>,这对你并没有什么帮助。 - Andrew Whitaker
我将整个文件反序列化为Base类型,其中subtests对象反序列化为List<Subtest>。在其中,数组可能再次包含“Base”或“Subtest”。我只需要找出一种方法来确定是否将其反序列化为基类(如果它包含测试套件)或Subtest(如果它是子测试)。我无法控制传入的数据,因此无法对其进行序列化。 - Levi Fuller
2
如果您的列表可能包含两种类型(SubtestBase),那么该属性不能是List <Subtest>。它必须是 List<object>List<BothInheritFromSomeBaseType的某些基本类型> 才能包含这两种类型。 - Andrew Whitaker
这很好,但是一旦我将对象反序列化为一个对象,如何确定对象列表应该反序列化为“Base”或“Subtest”?只需在对象上运行foreach并检查“subtests”是否是一个键即可吗? - Levi Fuller
@LeviFuller 它需要支持多态吗?试试这些由http://json2csharp.com/生成的扁平类。 - L.B
显示剩余3条评论
2个回答

3

我建议重新组织你的类,形成一个层次结构。我可能会漏掉一些属性,但你可以明白我的意思。最重要的是转换器。

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());

在我回答的时候没有看到这个。让我试一下,因为它似乎是一个更好的解决方案。谢谢Andrew! - Levi Fuller
@LeviFuller:当然,如果你有任何问题,请告诉我。 - Andrew Whitaker
非常 impressed with your code. 我试用了一下,它可以迭代到最后一个 Subtest,但是当它尝试退出(在第24次迭代结束时调用 Populate()),它会崩溃。此时,结果是一个 TestSuite,但它认为它应该是一个 Subtest。有什么想法吗?http://snag.gy/pouum.jpg - Levi Fuller
1
它是否成功反序列化其他“TestSuite”对象?像上面那样,TestSuite和Subtest是否有共同的基类?错误让我想到你有一个“List<Subtest>”,而不是一个“List<TestBase>”。 - Andrew Whitaker
安德鲁,我对你敬畏不已。希望有一天能达到你的水平。你是100%正确的,我忘记将类型从Subtest更改为TestBase... +10000赞给你。 - Levi Fuller

1
我最终做的是在我的Subtest类中添加了一个List属性,并在递归foreach循环函数中检查null值。虽然不如我想象的那么美观,但比逐个解析和反序列化每个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很陌生,无法理解它。希望这能帮助未来需要的人。如果您需要更多的解释,请在此处留下评论。

1
检查不同对象类型的空值是相当标准的,所以我认为没有人会对你处理它的方式感到震惊。几周前我也发现了同样的事情!当您无法控制数据模型时,情况会更糟。 - Lee Harrison

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