40

Below is a (slightly) stripped down response I get from a REST API upon successful creation of a new "job code" entry. I need to deserialize the response into some classes, but I'm stumped.

For reference, I'm using JSON.NET in .NET 3.5 (running in a SSIS script in SQL Server 2008 R2) to attempt my deserialization. Here's the JSON - which I obviously have no control over as it's coming from someone else's API:

{
   "results":{
      "jobcodes":{
         "1":{
            "_status_code":200,
            "_status_message":"Created",
            "id":444444444,
            "assigned_to_all":false,
            "billable":true,
            "active":true,
            "type":"regular",
            "name":"1234 Main Street - Jackson"
         },
         "2":{
            "_status_code":200,
            "_status_message":"Created",
            "id":1234567890,
            "assigned_to_all":false,
            "billable":true,
            "active":true,
            "type":"regular",
            "name":"4321 Some Other Street - Jackson"
         }
      }
   }
}

In my C# code, I do have a "JobCode" class defined which only partially maps the JSON values to properties - I'm not interested in all of the data that's returned to me:

[JsonObject]
class JobCode
{
    [JsonProperty("_status_code")]
    public string StatusCode { get; set; }
    [JsonProperty("_status_message")]
    public string StatusMessage { get; set; }
    [JsonProperty("id")]
    public string Id {get; set;}
    [JsonProperty("name")]
    public string Name { get; set; }

    //-------------------------------------------------------------------------------
    // Empty constructor for JSON serialization support
    //-------------------------------------------------------------------------------

    public JobCode() { }
}

I'm attempting to deserialize the data via this call:

newResource = JsonConvert.DeserializeObject<JobCode>(jsonResponse);

Where jsonResponse is the code outputted above.
When I execute the code, "newResource" always comes back as null - which is not unexpected because I know that there are actually multiple jobcodes in the data and this code is trying to deserialize it into a single JobCode object. I tried creating a new class called "JobCodes" that looks like this:

class JobCodes
{
    [JsonProperty("jobcodes")]
    public List<JobCode>_JobCodes { get; set; }
}

And then I tried calling this:

newResource = JsonConvert.DeserializeObject<JobCodes>(jsonResponse);

But the issue persists - my return object is null. What's throwing me off, I think, is the presence of the "1" and "2" identifiers. I don't know how to account for their presence in my object design and/or usage of the JSON.NET class / property attributes like [JsonObject],[JsonProperty], etc.

When I run the JSON data through JSON2CSharp, it constructs some weird-looking classes, so that hasn't proven too effective. I've validated the JSON with several different validators and it all checks out - I just don't know what I'm missing here.

Ultimately, I'd like to return a List from the JSON data, but I'm stumped on what I need to do to make that happen.

ale
  • 10,012
  • 5
  • 40
  • 49
Jake Bullet
  • 455
  • 1
  • 4
  • 9
  • already tried `var newResource = JsonConvert.DeserializeObject>(jsonResponse);//?` – ale Jul 31 '14 at 06:41
  • Your JSON is proper, but.. I suppose `results` and `jobcodes` should be a collection. In your case it is not. In this case you should prepare another class with `1` and `2` mapped properties of `JobCode` type. Then another class that Contains `jobcodes` property of type class, that is build prevousily. –  Jul 31 '14 at 06:44
  • There are no arrays in the JSON so you won't be able to deserialize into a list. – Anthony Chu Jul 31 '14 at 06:44
  • Here's an edited version of what I get: "Cannot deserialize the current JSON object into type 'System.Collections.Generic.List`1[JobCode]' 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." – Jake Bullet Jul 31 '14 at 06:52
  • @pwas - The problem is that there could be many more instances than just "1" and "2" - the response may include up to 50 "jobcode" objects at once. I'd be leery of hard-coding specific properties for "1" and "2" - unless I'm misunderstanding what you're suggesting. – Jake Bullet Jul 31 '14 at 06:58
  • @Jake Bullet I'm suggesting to make it an array (in JSON) instead of properties `1` and `2`. For example: `... "jobcodes": [{ \* one job code *\ }, { \* second jobcode *\ }]` and so on... –  Jul 31 '14 at 07:03
  • As I mentioned in the original post, that's not an option, unfortunately. I have no control over how the JSON is provided because it is coming from a third-party API. I can only work with the data as it's given to me... – Jake Bullet Jul 31 '14 at 07:06
  • Sorry, I've missed that. In this case I suggest you to play with `JObject` from Newtonsoft. Then you could manually iterate throu JSON properties `1`, `2` and so on (via LINQ) and then deserialize single `JobCode` from each property. –  Jul 31 '14 at 07:08
  • @Jake Bullet see ma answer :) –  Jul 31 '14 at 08:18
  • SQL Server 2006 now has new feature to deserialize your JSON string into table records. You probably wouldn't need Newton.Json library anymore. More info at https://msdn.microsoft.com/en-us/library/dn921897.aspx?wt.mc_id=AID533228_EML_4570601&f=255&MSPPError=-2147217396 – Jonas T Sep 30 '16 at 02:38

4 Answers4

77

Your problem is twofold:

  1. You don't have a class defined at the root level. The class structure needs to match the entire JSON, you can't just deserialize from the middle.
  2. Whenever you have an object whose keys can change, you need to use a Dictionary<string, T>. A regular class won't work for that; neither will a List<T>.

Make your classes like this:

class RootObject
{
    [JsonProperty("results")]
    public Results Results { get; set; }
}

class Results
{
    [JsonProperty("jobcodes")]
    public Dictionary<string, JobCode> JobCodes { get; set; }
}

class JobCode
{
    [JsonProperty("_status_code")]
    public string StatusCode { get; set; }
    [JsonProperty("_status_message")]
    public string StatusMessage { get; set; }
    [JsonProperty("id")]
    public string Id { get; set; }
    [JsonProperty("name")]
    public string Name { get; set; }
}

Then, deserialize like this:

RootObject obj = JsonConvert.DeserializeObject<RootObject>(json);

Working demo here

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • 1
    Thank you! This approach works as expected and all of my data parses as expected. In fact, I had tried something similar yesterday, but I missed using the Dictionary object instead of List<>. – Jake Bullet Jul 31 '14 at 18:13
27

Excellent Answers!

For those out there that may need some more help with the JSON Class Configuration, try: http://json2csharp.com/#

An excellent way of Auto Generating the Classes!

Or even easier, in VS, Goto:

Edit -> Paste Special -> Paste as JSON Classes

Rusty Nail
  • 2,692
  • 3
  • 34
  • 55
4

Because you can't change the scheme of JSON, and you can't set constant No. of properties, I'd suggest you to use JObject

var jobject = JObject.Parse(json);

var results = jobject["results"];
var jobcodes = results["jobcodes"];

var output = jobcodes.Children<JProperty>()
                     .Select(prop => prop.Value.ToObject<JobCode>())
                     .ToList();

Warning: code assumes, that JSON is always in proper schema. You should also handle invalid schema (for example where property is not of JobCode scheme).

Loofer
  • 6,841
  • 9
  • 61
  • 102
0

You can also deserialize your json to an object of your target class, and then read its properties as per normal:

var obj = DeSerializeFromStrToObj<ClassToSerialize>(jsonStr);
Console.WriteLine($"Property: {obj.Property}");

where DeSerializeFromStrToObj is a custom class that makes use of reflection to instantiate an object of a targeted class:

    public static T DeSerializeFromStrToObj<T>(string json)
    {
        try
        {
            var o = (T)Activator.CreateInstance(typeof(T));

            try
            {
                var jsonDict = JsonSerializer.Deserialize<Dictionary<string, string>>(json);

                var props = o.GetType().GetProperties();

                if (props == null || props.Length == 0)
                {
                    Debug.WriteLine($"Error: properties from target class '{typeof(T)}' could not be read using reflection");
                    return default;
                }

                if (jsonDict.Count != props.Length)
                {
                    Debug.WriteLine($"Error: number of json lines ({jsonDict.Count}) should be the same as number of properties ({props.Length})of our class '{typeof(T)}'");
                    return default;
                }

                foreach (var prop in props)
                {
                    if (prop == null)
                    {
                        Debug.WriteLine($"Error: there was a prop='null' in our target class '{typeof(T)}'");
                        return default;
                    }

                    if (!jsonDict.ContainsKey(prop.Name))
                    {
                        Debug.WriteLine($"Error: jsonStr does not refer to target class '{typeof(T)}'");
                        return default;
                    }

                    var value = jsonDict[prop.Name];
                    Type t = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
                    object safeValue = value ?? Convert.ChangeType(value, t);
                    prop.SetValue(o, safeValue, null); // initialize property
                }
                return o;
            }
            catch (Exception e2)
            {
                Debug.WriteLine(e2.Message);
                return o;
            }
        }
        catch (Exception e)
        {
            Debug.WriteLine(e.Message);
            return default;
        }
    }

A complete working example class can be found in my enhanced answer to a similar question, here

Paul Efford
  • 261
  • 4
  • 12