当JSON对象有些是字符串,有些是数组时,如何将JSON转换为列表字符串?

3

我的JSON格式如下:

{
"d": {
"__type": "CubeJsonData",
"SessionID": null,
"Code": 0,
"Message": "",
"Rows": {},
"Columns": {
  "Place 1": [
    0,
    1
  ],
  "Place 2": [
    0,
    2,
    4,
    6
  ],
},
"Set": [
  [
    "Number 1"
  ],
  [
    "Number 2"
  ],
  [
    "Number 3"
  ]
]
}
}

我需要获取以下数值。
List<string> Columns must contain: "Place 1", "Place 2"
List<string> Set must contain: "Number 1", "Number 2", "Number 3"

我的呼叫者是

var settings = new JsonSerializerSettings();
settings.Converters.Add(new AssosiativeArrayConverter());
var staffAlerts = JsonConvert.DeserializeObject<List<AlertDetails>>(jo.ToString(), settings);

我的Json转换器是
class AssosiativeArrayConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(string)) || (objectType == typeof(List<string>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            var l = new List<string>();
            reader.Read();
            while (reader.TokenType != JsonToken.EndArray)
            {
                l.Add(reader.Value as string);

                reader.Read();
            }
            return l;
        }
        else
        {
            return new List<string> { reader.Value as string };
        }
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {//to do
    }
}

我的职业是

class StaffDetail
{
    public string __type { get; set; }
    public string SessionID { get; set; }
    public string Code { get; set; }
    public string Message { get; set; }

    [JsonConverter(typeof(AssosiativeArrayConverter))]
    public List<string> Rows { get; set; }
    [JsonConverter(typeof(AssosiativeArrayConverter))]
    public List<string> Columns { get; set; }
    [JsonConverter(typeof(AssosiativeArrayConverter))]
    public List<string> Set { get; set; }
}

我收到了一个错误:
无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型“System.Collections.Generic.List`1[ReportingServicesAlerts.AlertDetails]”,因为该类型需要一个 JSON 数组(例如 [1,2,3])才能正确反序列化。要解决此错误,请将 JSON 更改为 JSON 数组(例如 [1,2,3]),或更改反序列化类型,使其成为可以从 JSON 对象反序列化的普通 .NET 类型(例如不是像整数这样的基元类型,也不是集合类型,如数组或 List)。还可以向类型添加 JsonObjectAttribute,以强制从 JSON 对象反序列化它。
您能帮我找出我做错了什么吗?

首先,你的 JSON 格式有错误,在“Set”之前不应该有逗号。我会稍后自己尝试解决问题 :) ... 但我相信你应该能够在不使用所有这些代码的情况下完成它... - frno
更正:)... "Set" 上面有两个生命。 - frno
2个回答

4
你有很多问题需要解决。首先,你收到这个错误的原因是:你的JSON包含一个单一的外部对象,但你试图将其反序列化为列表。这样是行不通的。如果JSON中只有一个对象,则需要反序列化为单个对象。
第二个问题是,你的StaffDetail类的数据不在JSON的顶层;它在外部JSON对象的d属性的值中。要解决此问题,你需要引入一个包装类并反序列化为该类。然后你可以从包装器中检索StaffDetail
第三个问题是,看起来你正在尝试将JSON中的ColumnsSet数据压缩成你的类中的List<string>属性。你正确地意识到需要使用转换器来完成此操作;但是,你的转换器没有正确处理JSON数据——它假设它将得到一个简单的字符串数组或一个简单的字符串。在JSON中,ColumnsSet都不是以这种方式结构化的,而且此外,两者的数据结构也不同。前者是一个包含属性的对象,其值是整数数组。后者是一个字符串数组的数组。由于它们是不同的结构,所以需要以不同的方式处理它们。在这种情况下,建议使用两个不同的转换器。
第四,虽然你正确地用[JsonConverter]属性装饰了StaffDetail类,以指示哪些属性应该使用你的转换器,但你错误地将转换器添加到序列化器设置中。这样做的问题是,你的转换器在CanConvert中说它可以处理任何字符串或任何字符串列表。如果你在设置中应用转换器,那么Json.Net将尝试在任何地方使用你的转换器,无论其是字符串还是字符串列表。显然,你不希望这样做——你的转换器只是为了处理一个特定的情况。
第五,看起来你还用[JsonConverter]属性装饰了Rows属性,但JSON显示为空对象。你是否关心此字段中的任何数据?如果不是,请将其声明为object;如果你确实关心,请提供可能在其中的示例。或者,如果你知道它的结构与ColumnsSet相同,则可以保持为List<string>并重用其中一个转换器。

您的问题中还存在一些小问题,例如由于额外的逗号导致您的JSON无效(已被@frno指出),以及您对JsonConvert.DeserializeObject()的调用提到了一个名为AlertDetails的类,但您展示的类实际上命名为StaffDetail。但我们将这些归咎于简单的复制粘贴错误。

呼~

好的,那么我们该如何解决这些问题呢?

让我们从您的类开始。我提到过您需要一个包装类,因为您的数据实际上在JSON中下降了一级;以下是它的样子:

class Wrapper
{
    public StaffDetail d { get; set; }
}

对于您的StaffDetail类,我已更改ColumnsSet属性以使用不同的转换器,因为每个JSON都不同。接下来,我将定义这些转换器。我还将Rows的类型更改为object并暂时删除了[JsonConverter]属性,因为从问题中不清楚该字段应如何处理。如果数据结构类似于ColumnsSet,则可以将其改回并使用适当的转换器,如我所提到的。
class StaffDetail
{
    public string __type { get; set; }
    public string SessionID { get; set; }
    public string Code { get; set; }
    public string Message { get; set; }
    public object Rows { get; set; }

    [JsonConverter(typeof(ColumnsConverter))]
    public List<string> Columns { get; set; }

    [JsonConverter(typeof(SetConverter))]
    public List<string> Set { get; set; }
}

以下是处理Columns数据的转换器。该转换器将接受一个JSON对象并将属性名称提取为字符串列表。

class ColumnsConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // this converter can handle converting some JSON to a List<string>
        return objectType == typeof(List<string>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Convert an object to a flat list of strings containing
        // just the property names from the object.
        JObject obj = JObject.Load(reader);
        return obj.Properties().Select(p => p.Name).ToList();
    }

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

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

这里有一个转换器,它将处理Set数据。该转换器将接受一个字符串数组的数组,并将其转换为一个平面化的字符串列表。
class SetConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        // this converter can handle converting some JSON to a List<string>
        return objectType == typeof(List<string>);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Convert an array of arrays of strings to a flat list of strings
        JArray array = JArray.Load(reader);
        return array.Children<JArray>()
            .SelectMany(ja => ja.Children(), (ja, jt) => jt.Value<string>()).ToList();
    }

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

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

要进行反序列化,您可以像这样调用JsonConvert.DeserializeObject()。请注意,我将其反序列化为Wrapper类,然后从中检索StaffDetail。还要注意,在这种情况下,我不需要(也不应该)将转换器传递给反序列化程序。通过StaffDetail类属性上的[JsonConverter]属性,在适当的时间自动选择它们。
StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;

这是一个简单的演示程序,展示了它的工作原理:
class Program
{
    static void Main(string[] args)
    {
        string json = @"
        {
            ""d"": {
                ""__type"": ""CubeJsonData"",
                ""SessionID"": null,
                ""Code"": 0,
                ""Message"": """",
                ""Rows"": {},
                ""Columns"": {
                    ""Place 1"": [
                        0,
                        1
                    ],
                    ""Place 2"": [
                        0,
                        2,
                        4,
                        6
                    ]
                },
                ""Set"": [
                    [
                        ""Number 1""
                    ],
                    [
                        ""Number 2""
                    ],
                    [
                        ""Number 3""
                    ]
                ]
            }
        }";

        StaffDetail detail = JsonConvert.DeserializeObject<Wrapper>(json).d;

        Console.WriteLine("Columns: " + string.Join(", ", detail.Columns));
        Console.WriteLine("Set: " + string.Join(", ", detail.Set));
    }
}

输出:

Columns: Place 1, Place 2
Set: Number 1, Number 2, Number 3

公共对象行 {获取;设置;} -- 这真的很有帮助。谢谢你详细解释了一切。这对我很有帮助,Brian。 - Raii
没问题;很高兴你觉得我的回答有帮助。 - Brian Rogers

1
你的Json有点奇怪,如果可以改一下就好了。不过,类是正确的: (+请不要忘记删除我所写的逗号)
public class Columns
{
    [JsonProperty(PropertyName="Place 1")]
    public List<int> Place1;
    [JsonProperty(PropertyName="Place 2")]
    public List<int> Place2;
}

public class Rows { }

public class D
{
    public string __type;
    public object SessionID;
    public int Code;
    public string Message;
    public Rows Rows;
    public Columns Columns;
    public List<List<string>> Set;
}
public class StaffDetail
{
    public D d { get; set; }
}

一个简单的方法来获取所有内容

var result = JsonConvert.DeserializeObject<StaffDetail>(json);

然后只需获取您想要的所有属性,例如
result.d.Columns.Place1[0] // for example

虽然这样反序列化不会出错,但实际上并不符合 OP 的要求。例如,他说他想要从“Columns”对象中获取属性的名称,而不是值。 - Brian Rogers
嗨,frno!关于逗号的问题...那只是一个复制粘贴的问题,哈哈。谢谢你的回答,但我恐怕它并不完全正确。但它帮助我学习了像这样的JSON属性--[JsonProperty(PropertyName="Place 1")]。谢谢! - Raii

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