将JSON字符串反序列化为C#类

3

好的,我会尽量简短明了地翻译。我从一个公共API获取了一个JSON字符串,看起来像这样:(只有两个示例的简短版本)

[{
    "$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
    "id": "bakerloo",
    "name": "Bakerloo",
    "modeName": "tube",
    "disruptions": [],
    "created": "2017-03-16T15:56:01.01Z",
    "modified": "2017-03-16T15:56:01.01Z",
    "lineStatuses": [
      {
        "$type": "Tfl.Api.Presentation.Entities.LineStatus, Tfl.Api.Presentation.Entities",
        "id": 0,
        "statusSeverity": 10,
        "statusSeverityDescription": "Good Service",
        "created": "0001-01-01T00:00:00",
        "validityPeriods": []
      }
    ],
    "routeSections": [],
    "serviceTypes": [
      {
        "$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
        "name": "Regular",
        "uri": "/Line/Route?ids=Bakerloo&serviceTypes=Regular"
      }
    ],
    "crowding": {
      "$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
    }
  },
  {
    "$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
    "id": "central",
    "name": "Central",
    "modeName": "tube",
    "disruptions": [],
    "created": "2017-03-16T15:56:01.01Z",
    "modified": "2017-03-16T15:56:01.01Z",
    "lineStatuses": [
      {
        "$type": "Tfl.Api.Presentation.Entities.LineStatus, Tfl.Api.Presentation.Entities",
        "id": 0,
        "statusSeverity": 10,
        "statusSeverityDescription": "Good Service",
        "created": "0001-01-01T00:00:00",
        "validityPeriods": []
      }
    ],
    "routeSections": [],
    "serviceTypes": [
      {
        "$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
        "name": "Regular",
        "uri": "/Line/Route?ids=Central&serviceTypes=Regular"
      },
      {
        "$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
        "name": "Night",
        "uri": "/Line/Route?ids=Central&serviceTypes=Night"
      }
    ],
    "crowding": {
      "$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
    }
  }]

目前为止还不错,接下来我将粘贴相关代码,我正在尝试将从 json2csharp 免费在线服务获取的JSON字符串反序列化为C#类。我尝试实现此操作的相关代码如下:

public async static Task<tubeStatusRootObject> GetTubeStatus(string url)
{
    var http = new HttpClient();
    var response = await http.GetAsync(url);
    var result = await response.Content.ReadAsStringAsync(); //This is working
    var deserializer = new DataContractJsonSerializer(typeof(tubeStatusRootObject));
    var ms = new MemoryStream(Encoding.UTF8.GetBytes(result));
    var data = (tubeStatusRootObject)deserializer.ReadObject(ms);
    return data; //all "data" properties are null
}

正如您可以从上面的评论中看到的,我在返回之前获取的所有内部数据都为null。

由json2csharp生成的类如下所示:

[DataContract]
public class Disruption
{
    [DataMember]
    public string type { get; set; }

    [DataMember]
    public string category { get; set; }

    [DataMember]
    public string categoryDescription { get; set; }

    [DataMember]
    public string description { get; set; }

    [DataMember]
    public string additionalInfo { get; set; }

    [DataMember]
    public string created { get; set; }

    [DataMember]
    public List<object> affectedRoutes { get; set; }

    [DataMember]
    public List<object> affectedStops { get; set; }

    [DataMember]
    public string closureText { get; set; }

    [DataMember]
    public bool? isWholeLine { get; set; }
}

[DataContract]
public class LineStatus
{
    [DataMember]
    public string type { get; set; }

    [DataMember]
    public int id { get; set; }

    [DataMember]
    public int statusSeverity { get; set; }

    [DataMember]
    public string statusSeverityDescription { get; set; }

    [DataMember]
    public string created { get; set; }

    [DataMember]
    public List<object> validityPeriods { get; set; }

    [DataMember]
    public string lineId { get; set; }

    [DataMember]
    public string reason { get; set; }

    [DataMember]
    public Disruption disruption { get; set; }
}

[DataContract]
public class ServiceType
{
    [DataMember]
    public string type { get; set; }

    [DataMember]
    public string name { get; set; }

    [DataMember]
    public string uri { get; set; }
}

[DataContract]
public class Crowding
{
    [DataMember]
    public string type { get; set; }
}

[DataContract]
public class tubeStatusRootObject
{
    [DataMember]
    public string type { get; set; }

    [DataMember]
    public string id { get; set; }

    [DataMember]
    public string name { get; set; }

    [DataMember]
    public string modeName { get; set; }

    [DataMember]
    public List<object> disruptions { get; set; }

    [DataMember]
    public string created { get; set; }

    [DataMember]
    public string modified { get; set; }

    [DataMember]
    public List<LineStatus> lineStatuses { get; set; }

    [DataMember]
    public List<object> routeSections { get; set; }

    [DataMember]
    public List<ServiceType> serviceTypes { get; set; }

    [DataMember]
    public Crowding crowding { get; set; }
}

显然,我只是添加了[DataContract][DataMember]。有没有人知道我做错了什么并能帮助我?
我按照Channel9上的示例进行操作。
请不要标记为重复问题,因为我找到了很多类似的问题,有些使用newtonsoft json,但我无法将那里的解决方案应用到我的示例中。

为什么不能使用Newtonsoft? - fredrik
不是我不能,只是我遵循了链接的示例,我想理解它是如何工作的,或者对于我的情况,为什么它不起作用:) - al1en
你说你不能用Newtonsoft让它工作,而不是不想。差别很大... - fredrik
我不知道DataContractJsonSerializer是否尊重类型字段,如果是这样的话可能会是罪犯。 - fredrik
此外,在某些情况下,您需要提供一个类列表,以便能够对其进行序列化和反序列化,而不仅仅是提供根类。 - fredrik
我建议您对当前类进行序列化,这样您就可以看到它在JSON中的样子,并进行调整和操作以更好地匹配JSON模式。 - Ori Nachum
3个回答

2
提供的示例数据描述了一个数组(tubeStatusRootObject[]),但当您尝试反序列化时,将其强制转换为单个实例,这是无效的转换。这就是为什么datanull的原因。
此外,如果有可用的工具来简化问题,就没有必要重新发明轮子。
static http = new HttpClient(); //reuse httpclient instead of creating a new one each time.
public async static Task<tubeStatusRootObject[]> GetTubeStatus(string url) {
    var response = await http.GetAsync(url); 
    var json = await response.Content.ReadAsStringAsync(); //This is working
    var data = Newtonsoft.Json.JsonConvert.DeserializeObject<tubeStatusRootObject[]>(json);
    return data;
}

我已经更改了我的代码以实现Newtonsoft.Json,并获得了结果。所以没有简单的方法可以在不实现Newtonsoft的情况下实现相同的结果吗? - al1en
2
@al1en,当然还有其他方法,但自己做会更加复杂。 - Nkosi
让我们来看看反序列化对象。就是这样! :D 使用Newtonsoft非常容易! - Prashanth Benny

1

你获取的对象有点复杂。

当它被反序列化(你现在所做的方式)时,它会查找与预期数据类型相同的对象的匹配名称。如果没有找到,则反序列化失败并返回null。这是非常确定的。

如果不使用Newtonsoft,你可以将每个嵌套对象的数据类型与某些通用类型进行匹配。或者需要执行一些字符串操作来自行反序列化(相当复杂)。

我建议对于这样的对象使用Newtonsoft.json。


我获取的整个对象可以在此处查看:https://api.tfl.gov.uk/Line/Mode/tube/Status - 但是对象本身每次都会不同,这取决于一些干扰和类似线路的更改。 - al1en

1
正如其他人所说,你需要将其反序列化为一个数组,而不仅仅是定义为响应的类型的单个实例,因为响应是一个数组。
如果你将响应读入字符串中,那么 Json.Net 只需要一行响应。
var data= Newtonsoft.Json.JsonConvert.DeserializeObject<tubeStatusRootObject[]>(result);

使用DataContractJsonSerializer时,需要写3行或更多代码来完成相同的操作。

var deserializer = new DataContractJsonSerializer(typeof(tubeStatusRootObject[]));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(result)))
{
    var data = (tubeStatusRootObject[])deserializer.ReadObject(ms);
}

(注意使用using包装器确保MemoryStream被处理。)
如果您直接将HTTP响应流读取到反序列化程序中,您将无法节省代码行数。
using (var s = http.GetStreamAsync(url).Result)
using (var sr = new StreamReader(s))
using (var reader = new Newtonsoft.Json.JsonTextReader(sr))
{
    var serializer = new Newtonsoft.Json.JsonSerializer();
    var data = serializer.Deserialize<tubeStatusRootObject[]>(reader);
}

对抗
using (var stream = await response.Content.ReadAsStreamAsync())
{
    var dcjs = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(tubeStatusRootObject[]));

    var data= (tubeStatusRootObject[])dcjs.ReadObject(stream);
}

另一个你可能需要考虑的是性能。Json.Net声称以下内容

Json Serializers performance comparison graph


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