2

On some occasions when I receive JSON whose one of the array property is empty the deserialization fails, throwing the following exception :

Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'SonicApi.ClickMark[]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly.

To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object.

Path auftakt_result.click_marks, line 1, position 121.

Trying to ignore null values with the following code didn't help:

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

Here is an example of JSON that produces the error :

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

Here is an example of JSON whose array is not empty and does not produce any error:

{
    "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 ...

Here are the C# types representing above objects:

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

}

How can I deserialize responses whose click_marks content is null ?

If that matters, I am using the latest version of Newtonsoft.Json : v6.0

EDIT

Here is the adopted solution according @khellang's answer :

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");
    }
}
1
  • I've added it at the bottom :D Commented Feb 25, 2014 at 21:59

1 Answer 1

7

It has nothing to do with null values (none of your JSON examples have null values for any property). You're trying to deserialize a JSON object into a ClickMark[]:

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

The reason it works for example number two is that the click_marks property actually is an array of ClickMark objects:

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

Where does the data come from? You need to make sure that the click_marks property is either an array or an object, not both, and that your typed C# object ClickMarks matches the "type" of the JSON property.

If you have no control over the data, e.g. if it comes from a 3rd party, I'd suggest you write a custom JsonConverter that you can apply to that single property:

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

And apply it like this:

public sealed class AuftaktResult
{
    // ...

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

This will check if the value is a single object and wrap it in an array so it will match your C# POCO property :)

Sign up to request clarification or add additional context in comments.

3 Comments

Data comes from the following service : sonicapi.com/docs/api/analyze-tempo. Yes but what am I supposed to change ? it's only on rare occasions that it does fail.
It's almost that ! The thing is that it populates the array with an item while there should not be any items in the array, I'm trying to fix it based on your answer.
Exactly my problem! JSON is coming from 3rd party and no control - this worked effectively. Nice work!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.