Rizki Pratama
Rizki Pratama

Reputation: 571

Deserialize JSON string that sometimes resulting a list of object or an empty object

I have a JSON string returned from API that sometimes like this, in which the "result" key only has an empty object :

{
  "_meta": {
    "status": "SUCCESS",
    "count": 0
  },
  "result": {}
}

or this, in which the "result" key has an array of object:

{
  "_meta": {
    "status": "SUCCESS",
    "count": 0
  },
  "result": [
    { ... },
    { ... }
  ]
}

I've tried implementing custom JsonConverter from this question, but I guess I got no luck. Here is my implementation:

class EmptyObjectJsonConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        object retObj = new Object();

        if (reader.TokenType == JsonToken.StartArray)
        {
            retObj = serializer.Deserialize<List<T>>(reader);
        }
        else if (reader.TokenType == JsonToken.StartObject)
        {
            retObj = new List<T>();
        }

        return retObj;
    }

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

And I use it this way:

public class ApiReturn
{
    public ApiMeta _Meta { get; set; }

    [JsonConverter(typeof(EmptyObjectJsonConverter<ApiResult>))]
    public List<ApiResult> Result { get; set; }
}

What I want to achieve from above implementation is: if the token is start array, then deserialize it in a normal way, but if the token is start object, then just return an empty list of object.

But then, when I tried running the program with the "result" key having an empty object, it throws exception: Additional text found in JSON string after finishing deserializing object., and the resulting ApiReturn.Result is null.

Did I implement it in a wrong way? Any help would be appreciated.


UPDATE based on @A. Chiesa answer: Please look at my implementation of GetApiResult:

[JsonProperty("result")]
public JToken Result { get; set; }

public T GetResultAs<T>()
{
    var objectReturned = default(T);

    try
    {
        if (Result.Type == JTokenType.Array)
        {
            objectReturned = Result.ToObject<T>();
        }
        else if (Result.Type == JTokenType.Object)
        {
            // Should it return an empty List<ApiResult> if I use type List<ApiResult> ?
            objectReturned = default(T);
        }
    }
    catch
    {
        objectReturned = default(T);
    }

    return objectReturned;
}

Why I get null when the JSON node token is object? Should it return an empty List<ApiResult> when I use GetResultAs<List<ApiResult>>?

Upvotes: 2

Views: 2236

Answers (3)

Rizki Pratama
Rizki Pratama

Reputation: 571

Here is my full working implementation of @A. Chiesa's answer that fits my needs if you want to know.

At first I thought that default(T) is the same as creating a new instance 😂

So here I use instantiation of generic type (it's new for me: C# Create New T()):

public class ApiReturn
{
    [JsonProperty("_meta")]
    public ApiMeta Meta { get; set; }

    [JsonProperty("result")]
    public JToken Result { get; set; }

    public T GetResultAs<T>() where T : new()
    {
        var objectReturned = default(T);

        try
        {
            if (Result.Type == JTokenType.Array)
            {
                objectReturned = Result.ToObject<T>();
            }
            else if (Result.Type == JTokenType.Object)
            {
                objectReturned = new T();
            }
        }
        catch
        {
            objectReturned = new T();
        }

        return objectReturned;
    }
}

Upvotes: 0

Alberto Chiesa
Alberto Chiesa

Reputation: 7360

I like to use a JToken property, abstracted by the deserialization class.

A really naive implementation would be:

public enum MetaStatus
{
    SUCCESS
    // FAILURE,
    // ... etc
}

public class Meta
{
    public MetaStatus Status { get; set; }
    public long Count { get; set; }
}

public class ApiResponse
{
    [JsonProperty("_meta")]
    public Meta Meta { get; set; }

    [JsonProperty("result")]
    public JToken JsonResult { get; set; }

    public T GetResultAs<T>()
    {
        // put in place some error handling logic, depending upon what's really an error
        // in this method.
        try
        {
            return JsonResult.ToObject<T>();
        }
        catch
        {
            return default(T);
        }
    }
}

In short: you ask Json.Net to keep the result as a Json object, then, after the deserialization is performed, you can try deserializing the JToken every way you want, just calling a typed GetResultAs, like GetResultAs<List<ApiLineObj>>().

The code is not meant to be used as-is (probably you want to handle some check), but the basic deserialization mechanic is there.

Upvotes: 1

MetalJacketNL
MetalJacketNL

Reputation: 109

You can try to deserialize the JSON to an Object right away. I used json2sharp to generate the classes from your JSON code.

public class Meta
{
    public string status { get; set; }
    public int count { get; set; }
}

public class Result
{
    <Add whatever field you are expecting>
}

public class RootObject
{
    public Meta _meta { get; set; }
    public Result result { get; set; }
}

Then you can use the line below to deserialze the JSON to an Object right away.

RootObject root = JsonConvert.DeserializeObject<RootObject>("YOUR JSON");

If the result array is empty the list in the RootObject class will just be empty.

Upvotes: 0

Related Questions