【问题标题】:JSON deserialization fails only when array is empty仅当数组为空时,JSON 反序列化才会失败
【发布时间】:2014-03-28 10:25:17
【问题描述】:

在某些情况下,当我收到其中一个数组属性为空的 JSON 时,反序列化失败,抛出以下异常:

无法将当前 JSON 对象(例如 {"name":"value"})反序列化为类型“SonicApi.ClickMark[]”,因为该类型需要 JSON 数组(例如 [1,2,3])正确反序列化。

要修复此错误,要么将 JSON 更改为 JSON 数组(例如 [1,2,3]),要么将反序列化类型更改为正常的 .NET 类型(例如,不是像整数这样的原始类型,而不是像数组或列表这样的集合类型)可以从 JSON 对象反序列化。也可以将 JsonObjectAttribute 添加到类型中以强制它从 JSON 对象反序列化。

路径 auftakt_result.click_marks,第 1 行,位置 121。

尝试使用以下代码忽略空值没有帮助:

var jsonSerializerSettings = new JsonSerializerSettings();
jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore;

这是一个产生错误的 JSON 示例:

{
    "status": {
        "code": 200
    },
    "auftakt_result": {
        "clicks_per_bar": 0,
        "overall_tempo": 0,
        "overall_tempo_straight": 0,
        "click_marks": {}
    }
}

这里是一个 JSON 的例子,它的数组不为空并且不会产生任何错误:

{
    "status": {
        "code": 200
    },
    "auftakt_result": {
        "clicks_per_bar": 8,
        "overall_tempo": 144.886978,
        "overall_tempo_straight": 144.90889,
        "click_marks": [
            {
                "index": 0,
                "bpm": 144.226624,
                "probability": 0.828170717,
                "time": 0.0787981859,
                "downbeat": "false"
            },
            {
                "index": 1,
                "bpm": 144.226517,
                "probability": 0.831781149,
                "time": 0.286802721,
                "downbeat": "false"
            },
etc ...

以下是表示上述对象的 C# 类型:

public sealed class AnalyzeTempoResponse
{
    [JsonProperty("auftakt_result")]
    public AuftaktResult AuftaktResult { get; set; }

    [JsonProperty("status")]
    public Status Status { get; set; }
}

public sealed class Status
{
    [JsonProperty("code")]
    public int Code { get; set; }
}

public sealed class AuftaktResult
{
    [JsonProperty("clicks_per_bar")]
    public int ClicksPerBar { get; set; }

    [JsonProperty("overall_tempo")]
    public double OverallTempo { get; set; }

    [JsonProperty("overall_tempo_straight")]
    public double OverallTempoStraight { get; set; }

    [JsonProperty("click_marks")]
    public ClickMark[] ClickMarks { get; set; }
}

public sealed class ClickMark
{
    [JsonProperty("index")]
    public int Index { get; set; }

    [JsonProperty("bpm")]
    public double Bpm { get; set; }

    [JsonProperty("probability")]
    public double Probability { get; set; }

    [JsonProperty("time")]
    public double Time { get; set; }

    [JsonProperty("downbeat")]
    public string Downbeat { get; set; }

}

如何反序列化 click_marks 内容为空的响应?

如果这很重要,我使用的是最新版本的 Newtonsoft.Json : v6.0

编辑

这是根据@khillang 的回答采用的解决方案:

public class ClickMarkArrayConverter : CustomCreationConverter<ClickMark[]>
{
    public override ClickMark[] Create(Type objectType)
    {
        return new ClickMark[] {};
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            return serializer.Deserialize(reader, objectType);
        }

        if (reader.TokenType == JsonToken.StartObject)
        {
            serializer.Deserialize(reader); // NOTE : value must be consumed otherwise an exception will be thrown
            return null;
        }

        throw new NotSupportedException("Should not occur, check JSON for a new type of malformed syntax");
    }
}

【问题讨论】:

  • 我已经在底部添加了它:D

标签: c# arrays json exception deserialization


【解决方案1】:

它与 null 值无关(您的 JSON 示例都没有 any 属性的 null 值)。您正在尝试将 JSON 对象反序列化为 ClickMark[]

"click_marks": {} // <-- This is an object, not null, not an array.

例如第二个例子,它起作用的原因是click_marks 属性实际上一个ClickMark 对象的数组:

"click_marks": [{...}, {...}, {...}] // <-- This is an array with three objects.

数据从何而来?您需要确保 click_marks 属性是数组或对象,而不是两者,并且您的类型化 C# 对象 ClickMarks 与 JSON 属性的“类型”匹配。

如果您无法控制数据,例如如果它来自第 3 方,我建议您编写一个自定义 JsonConverter,您可以将其应用于该单一属性:

public class ObjectToArrayConverter<T> : CustomCreationConverter<T[]>
{
    public override T[] Create(Type objectType)
    {
        return new T[0];
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        if (reader.TokenType == JsonToken.StartArray)
        {
            return serializer.Deserialize(reader, objectType);
        }
        else
        {
            return new T[] { serializer.Deserialize<T>(reader) };
        }
    }
}

并像这样应用它:

public sealed class AuftaktResult
{
    // ...

    [JsonProperty("click_marks")]
    [JsonConverter(typeof(ObjectToArrayConverter<ClickMark>))]
    public ClickMark[] ClickMarks { get; set; }
}

这将检查该值是否为单个对象并将其包装在一个数组中,以便它与您的 C# POCO 属性匹配:)

【讨论】:

  • 数据来自以下服务:sonicapi.com/docs/api/analyze-tempo。是的,但我应该改变什么?只有在极少数情况下才会失败。
  • 差不多就是这样!问题是它用一个项目填充数组,而数组中不应该有任何项目,我正在尝试根据您的回答修复它。
  • 正是我的问题! JSON 来自第 3 方并且无法控制 - 这很有效。干得好!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多