manpreets
manpreets

Reputation: 25

JsonConvert.DeserializeObject not converting a response object from JSON as expected

I am creating an ActiveCampaign V1.1 wrapper for C#.NET and creating a class that lists the ContactLists using (https://www.activecampaign.com/api/example.php?call=list_list)

The response object returned after JsonConvert.DeserializeObject<BasicListResponse>(JSON) does have non-list properties populated, but the list portion of the JSON is not converted.

I have tried the following BasicListResponse implementations:

public class Result
{
    [JsonProperty("result_code")]
    public int ResultCode { get; set; }

    [JsonProperty("result_message")]
    public string ResultMessage { get; set; }

    [JsonProperty("result_output")]
    public string ResultOutput { get; set; }
}

public class BasicListResponse : Result
{      
    public Dictionary<string, BasicList> list { get; set; }
}

public class BasicList
{
    [JsonProperty("id")]
    public string Id { get; set; }

    [Required]
    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("cdate")]
    public DateTime CreatedOn { get; set; }

    [JsonProperty("private")]
    public bool Private { get; set; }

    [JsonProperty("userid")]
    public string UserId { get; set; }

    [JsonProperty("subscriber_count")]
    public int SubscriberCount { get; set; }
}

I have also tried to use json2csharp or VS > Paste Special but the output has numbered classes rather than a list of objects.

The JSON response is as follows:

{
    "0": {
        "id": "1",
        "name": "xxxxx xxxxxx xxxxxxx",
        "cdate": "2019-10-27 22:43:23",
        "private": "0",
        "userid": "1",
        "subscriber_count": 1
    },
    "1": {
        "id": "2",
        "name": "yyyyy yyyyy yyyyy",
        "cdate": "2019-10-27 22:44:03",
        "private": "0",
        "userid": "1",
        "subscriber_count": 0
    },
    "result_code": 1,
    "result_message": "Success: Something is returned",
    "result_output": "json"
}

I extracted the Dictionary<T,T> in the wrapper but the list is always NULL.

If I re-serialize the BasicListResponse the results are as follows

{ "list": null, "result_code": 1, "result_message": "Success: Something is returned", "result_output": "json" }

I expect the BasicListResponse to be used as a root object containing a List/Array with three string result_* properties.

I appreciate any help to resolve this.

Upvotes: 1

Views: 2999

Answers (3)

Brian Rogers
Brian Rogers

Reputation: 129827

Your JSON basically represents a dictionary of string-BasicList pairs, comingled with some other properties about the overall result. This format makes it more difficult to work with. (It would have been better if the dictionary data were in child object in the JSON.)

The easiest way to handle this situation is to take advantage of Json.Net's "Extension Data" feature.

Here's what you would need to do:

  1. Create a private Dictionary<string, JToken> property in your BasicListResponse class and mark it with the [JsonExtensionData] attribute. This will catch the dictionary data during deserialization.
  2. Change your list property be a List<BasicList> instead of a Dictionary<string, BasicList>. (I would also rename it with proper capitalization to be consistent with your other properties.)
  3. Add a private OnDeserialized method as shown below to populate the List<BasicList> from the extension dictionary at the end of the deserialization process.

So your BasicListResponse class should look like this:

public class BasicListResponse : Result
{
    public List<BasicList> List { get; set; }

    [JsonExtensionData]
    private Dictionary<string, JToken> Data { get; set; }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        List = Data?.OrderBy(kvp => kvp.Key)
                    .Select(kvp => kvp.Value.ToObject<BasicList>())
                    .ToList();
    }
}

There is one other small problem we need to address: in your BasicList class you have defined the Private property as bool, but in the JSON it is a string containing a number. This will cause deserialization to fail due to incompatible types. To make it work, you can either change Private to be a string and then handle the interpretation of the value elsewhere in your code, or you can do something like this:

public class BasicList
{
    ...

    [JsonProperty("private")]
    private string PrivateAsString { get; set; }

    [JsonIgnore]
    public bool Private
    {
        get { return PrivateAsString != "0"; }
        set { PrivateAsString = value ? "1" : "0"; }
    }

    ...
}

With these changes you can deserialize to your BasicListResponse class and everything should work properly.

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

Working demo here: https://dotnetfiddle.net/9MmSuG

Upvotes: 3

RoadRunner
RoadRunner

Reputation: 26335

Not the most elegant solution, but you could structure your classes like this:

public class Results
{
    [JsonExtensionData]
    public IDictionary<string, JToken> Items;

    [JsonProperty("result_code")]
    public long ResultCode { get; set; }

    [JsonProperty("result_message")]
    public string ResultMessage { get; set; }

    [JsonProperty("result_output")]
    public string ResultOutput { get; set; }
}

public class Result
{
    public long Id { get; set; }

    public string Name { get; set; }

    public DateTime Cdate { get; set; }

    public long Private { get; set; }

    public long UserId { get; set; }

    public long SubscriberCount { get; set; }

    public override string ToString()
    {
        return $"Id={Id},Name={Name},CDate={Cdate.ToString()},Private={Private},UserId={UserId},SubscriberCount={SubscriberCount}";
    }
}

Then you could deserialize your json and map your IDictionary<string, JToken> Items to a IEnumerable<Result>. Note that JsonExtensionData needs to be IDictionary<string, JToken> in this case.

var deserializedJson = JsonConvert.DeserializeObject<Results>(json);

var results = deserializedJson
    .Items
    .Select(entry => new Result
    {
        Id = (long)entry.Value["id"],
        Name = (string)entry.Value["name"],
        Cdate = DateTime.TryParse((string)entry.Value["cdate"], out var cdate) ? cdate : DateTime.MinValue,
        Private = (long)entry.Value["private"],
        UserId = (long)entry.Value["userid"],
        SubscriberCount = (long)entry.Value["subscriber_count"]
    });

Console.WriteLine($"result_code={deserializedJson.ResultCode},result_message={deserializedJson.ResultMessage},result_output={deserializedJson.ResultOutput}");
foreach (var result in results)
{
    Console.WriteLine(result.ToString());
}

Output:

result_code=1,result_message=Success: Something is returned,result_output=json
Id=1,Name=xxxxx xxxxxx xxxxxxx,CDate=27/10/2019 10:43:23 PM,Private=0,UserId=1,SubscriberCount=1
Id=2,Name=yyyyy yyyyy yyyyy,CDate=27/10/2019 10:44:03 PM,Private=0,UserId=1,SubscriberCount=0

Upvotes: 0

Shan Nautiyal
Shan Nautiyal

Reputation: 129

Try

"list":{
    "0": {
        "id": "1",
        "name": "xxxxx xxxxxx xxxxxxx",
        "cdate": "2019-10-27 22:43:23",
        "private": "0",
        "userid": "1",
        "subscriber_count": 1
    },
    "1": {
        "id": "2",
        "name": "yyyyy yyyyy yyyyy",
        "cdate": "2019-10-27 22:44:03",
        "private": "0",
        "userid": "1",
        "subscriber_count": 0
    },
    "result_code": 1,
    "result_message": "Success: Something is returned",
    "result_output": "json"
}

Upvotes: -1

Related Questions