【问题标题】:How can I deserialize JSON when the object type is not known beforehand?当事先不知道对象类型时,如何反序列化 JSON?
【发布时间】:2019-10-31 20:06:08
【问题描述】:

所以我有一个正在开发的 API 和前端,当其中一个值可能是多个不同类型的列表时,我需要一种反序列化 JSON 的方法。目前我正在将其反序列化为List<dynamic>,但在我的上下文中使用它是一个巨大的痛苦。

public class WorkbenchAPI   
{
    public string status { get; set; }
    public HttpStatusCode code { get; set; }
    public string error { get; set; }
    public string guid { get; set; }
    public List<dynamic> results { get; set; }
}

示例 JSON

{
    "status": "ok",
    "code": 200,
    "error": null,
    "guid": "1234",
    "results": [
        {
            "SamAccountName": "dizbuster",
            "CN": "Buster, Diz",
            "EmailAddress": "dizbuster@whatever.com",
        }
    ]
}

在上面的 JSON 示例中,结果应该被反序列化为类型 List&lt;ClassA&gt;,例如。

{
    "status": "ok",
    "code": 200,
    "error": null,
    "guid": "1234",
    "results": [
        {
            "data": "127.0.0.1",
            "owner": "dizbuster",
            "email": "dizbuster@whatever.com",
        },
        {
            "data": "192.168.0.1",
            "owner": "dizbuster",
            "email": "dizbuster@whatever.com",
        }
    ]
}

在此示例中,结果应反序列化为List&lt;ClassB&gt;

有些字段名称可能会以不同的类型显示,但有些则不会。从功能上讲,它们是两种不同的类型,表示来自两个单独的 API 调用的输出,因此我希望将其反序列化为特定的对象类型,而不是使用 dynamic 或包含所有可能字段的整体对象。

目前我正在使用List&lt;dynamic&gt; 并将其序列化回一个临时的JSON 字符串,然后将其再次反序列化为List&lt;ClassA&gt; 或其他。这似乎是一种不好的方法,并且使用起来很麻烦。

有没有更好的方法来构造它或处理序列化/反序列化?我对两端都有完全的控制权,所以如果需要的话我也不会太难改变 JSON。另外,我知道一个特定的 API 调用将返回看起来像这样的 JSON,而另一个 API 调用将返回看起来像这样的 JSON。每个案例都应该产生一个不同类型的列表

【问题讨论】:

  • let myObj = JSON.parse(response); let myList = myObj.results; 怎么样?这将返回一个 JS 数组(与一些强类型的 List&lt;&gt; 相比)。这还不够吗?
  • 从功能上讲,它们是两种不同的类型,表示来自两个单独的 API 调用的输出——这是否意味着您提前知道 results 类型?
  • 因为如果您事先知道类型,将WorkbenchAPI 更改为通用WorkbenchAPI&lt;T&gt; { public List&lt;T&gt; results { get; set; } // ... 是可行的方法。
  • @dbc 我知道对于给定类型的 JSON 结构是什么。我知道一个特定的 API 调用将返回类似于 this 的 JSON,而另一个 API 调用将返回类似于 that 的 JSON。每个案例都应该产生一个不同类型的列表

标签: c# .net json


【解决方案1】:

由于您事先知道每个 API 调用在 "results" 中返回的数据类型,您可以将您的 WorkbenchAPI 类设为通用:

public abstract class WorkbenchAPI
{
    protected abstract IReadOnlyCollection<dynamic> GetResults();

    public string status { get; set; }
    public HttpStatusCode code { get; set; }
    public string error { get; set; }
    public string guid { get; set; }
    public IReadOnlyCollection<dynamic> results => GetResults();
}

public class WorkbenchAPI<T> : WorkbenchAPI where T : class
{
    protected override IReadOnlyCollection<dynamic> GetResults() => results;
    public new List<T> results { get; set; } = new List<T>();
}

注意事项:

  • 我将通用属性提取到一个抽象基类中,以允许它们被预先存在的不关心特定结果类型的非泛型代码访问。

  • 我还在基类中添加了一个IReadOnlyCollection&lt;dynamic&gt; results 以简化与预先存在的代码的集成,例如:

    // Deserialize to the concrete, known type
    var root = JsonConvert.DeserializeObject<WorkbenchAPI<ClassA>>(json);
    
    // Access the results in a type-safe manner:
    var accounts = root.results.Select(r => r.SamAccountName).ToList();
    
    // Upcast to the base class in code where we don't need to know about the specific result type.
    // E.g.: accessing root properties like guid or getting the result count.
    WorkbenchAPI baseRoot = root;
    var count = baseRoot.results.Count;
    var guid = baseRoot.guid;
    

    但是,将结果访问权限添加为 dynamic 对象的非泛型只读集合是可选的,如果您的旧代码可以重写为泛型,则可以将其删除。

这里是演示小提琴:https://dotnetfiddle.net/lbTs1z

【讨论】:

  • 这正是我所需要的。我之前没有在这件事上使用过&lt;T&gt;,但是在阅读了小提琴并玩弄了它之后,我现在理解它并完美地满足了我的需求。
猜你喜欢
  • 1970-01-01
  • 2011-07-03
  • 2010-10-21
  • 1970-01-01
  • 1970-01-01
  • 2012-02-29
  • 2014-03-27
  • 2015-06-23
相关资源
最近更新 更多