9

I'm downloading the JSON using System.Net.WebClient.DownloadString. I'm getting a valid response:

{
"FormDefinition": [
    {
        "$id":"4",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form"
    },
    {
        "$id":"6",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form test second"
    },
    {
        "$id":"46",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"any_Name"
    },
    {
        "$id":"47",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Punchworks Form test second"
    },
    {
        "$id":"49",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name ??´????? ???? ACEeišuu { [ ( ~ ! @ # "
    },
    {
        "$id":"50",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"something new"
    },
    {
        "$id":"56",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name руÌÑÑкий 汉语漢語 ĄČĘėįšųū { [ ( ~ ! @ # "
    },
    {
        "$id":"57",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test Name"
    },
    {
        "$id":"58",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 12:59:29 PM"
    },
    {
        "$id":"59",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:01:18 PM"
    },
    {
        "$id":"60",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:40:44 PM"
    },
    {
        "$id":"61",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:43:46 PM"
    },
    {
        "$id":"62",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:48:21 PM"
    },
    {
        "$id":"63",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:57:00 PM"
    },
    {
        "$id":"64",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:57:53 PM"
    },
    {
        "$id":"65",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Unique Name - 12/16/2013 1:58:46 PM"
    },
    {
        "$id":"79",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name1211"
    },
    {
        "$id":"80",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Testing Name1211"
    },
    {
        "$id":"81",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"any_nami"
    },
    {
        "$id":"90",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test_something3"
    },
    {
        "$id":"91",
        "Class":558,
        "ClassDisplayLabel":"Punchworks",
        "Name":"Test_something4"
    }]
}

And here is my Model:

public class FormDefinitionList
{
    [JsonProperty("FormDefinition")]
    public List<FormDefinition> FormDefinitions { get; set; }
}

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

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

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

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

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

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

Everything works when I do:

string response = "json as above";
FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList> (response);

except that the Id ($id) property is always null. At first I tried to figure out if the dollar sign symbol I was getting back from the server was different, but that doesn't appear to be the case. I'm not sure where to go from here, so any ideas?

Thanks in advance.

NOTE: If I try to deserialize with something like JavaScriptSerializer, it works perfectly, so I'm fairly sure it's something wrong with my model or with JSON.net. Could be wrong though.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
itslittlejohn
  • 1,808
  • 3
  • 20
  • 33
  • Try without the `$`. I'd recommend against having special characters as keys. – Brad M Dec 19 '13 at 16:10
  • 2
    @BradM Thanks for the suggestion. Unfortunately, that's not an option as the Api is outside of my control. And though it may not be recommended, it is done quite often and shouldn't be the cause of this problem. Any other ideas? – itslittlejohn Dec 19 '13 at 16:12

3 Answers3

15

Json.Net normally uses $id along with $ref as metadata to preserve object references in JSON. So when it sees $id it assumes that property is not part of the actual JSON property set, but an internal identifier. Thus it does not populate the Id property on your object, even though you included a [JsonProperty] attribute indicating that it should.

UPDATE

As of Json.Net version 6.0.4, there is a new setting by which you can instruct the deserializer to treat these "metadata" properties as normal properties instead of consuming them. All you need to do is set the MetadataPropertyHandling setting to Ignore and then deserialize as usual.

var settings = new JsonSerializerSettings();
settings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;

var obj = JsonConvert.DeserializeObject<FormDefinitionList>(json, settings);

Prior to version 6.0.4, a workaround was needed to solve this issue. The rest of this answer discusses the possible workarounds. If you are using 6.0.4 or later, you do not need the workaround and can stop reading now.


The simplest workaround I can see is to do a string replace of "$id" with "id" (including the quotes) on the JSON prior to deserializing it, as @Carlos Coelho suggested. Since you would have to do this with each response, if you go this route I would recommend making a simple helper method to avoid code duplication, e.g.:

public static T Deserialize<T>(string json)
{
    return JsonConvert.DeserializeObject<T>(json.Replace("\"$id\"", "\"id\""));
}

However, since you said in your comments that you are not so keen on the idea of using a string replace, I looked into other options. I did find one other alternative that might work for you-- a custom JsonConverter. The idea behind the converter is that it would try to use Json.Net's built-in deserialization mechanisms to create and populate the object (sans ID), then manually retrieve the $id property from the JSON and use it to populate the Id property on the object via reflection.

Here is the code for the converter:

public class DollarIdPreservingConverter : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(FormDefinition);
    }

    public override object ReadJson(JsonReader reader, Type objectType,
                           object existingValue, JsonSerializer serializer)
    {
        JObject jo = JObject.Load(reader);
        object o = jo.ToObject(objectType);
        JToken id = jo["$id"];
        if (id != null)
        {
            PropertyInfo prop = objectType.GetProperty("Id");
            if (prop != null && prop.CanWrite && 
                prop.PropertyType == typeof(string))
            {
                prop.SetValue(o, id.ToString(), null);
            }
        }
        return o;
    }

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

I tried to write the converter such that it would work for any object that has the $id -- you just need to change the CanConvert method accordingly so that it returns true for all the types that you need to use it for in addition to FormDefinition.

To use the converter, you just need to pass an instance of it to DeserializeObject<T> like this:

FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList>(
                                      json, new DollarIdPreservingConverter());

Important note: you might be tempted to decorate your classes with a JsonConverter attribute instead of passing the converter into the DeserializeObject call, but don't do this-- it will cause the converter to go into a recursive loop until the stack overflows. (There is a way to get the converter to work with the attribute, but you would have to rewrite the ReadJson method to manually create the target object and populate its properties instead of calling jo.ToObject(objectType). It is doable, but a little more messy.)

Let me know if this works for you.

Brian Rogers
  • 125,747
  • 31
  • 299
  • 300
  • Thanks for going the extra mile, Brian. I tested your converter and it works like a charm. I did have a syntax issue though with `prop.SetValue(o, id.ToString());` I needed to include `null` as a final third parameter to get it to build. Does that make sense to you? – itslittlejohn Dec 20 '13 at 13:41
  • I'm guessing you must be using an older version of the .Net framework (I'm on 4.5). The third parameter of `SetValue` is used in case the property happens to be an indexer, which here it is not. So yes, it makes sense, and is correct, to pass null in the third parameter to get it to work. – Brian Rogers Dec 20 '13 at 15:11
0

The problem is the $ sign, therefore a workaround would be:

Remove the $ from the JsonProperty annotation.

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

On your code, replace the special character $

string response = "json as above";
FormDefinitionList root = JsonConvert.DeserializeObject<FormDefinitionList> (response.Replace("$id","id"));

Edited as @BrianRogers suggests

Carlos Coelho
  • 566
  • 1
  • 5
  • 14
  • I would like to point out that the $ should be perfectly allowed by JSON.net: http://stackoverflow.com/questions/4638585/how-do-c-sharp-classes-deal-with-dollar-signs-in-json. Using String.replace is clearly not a good option as I may have the $ character in my response body that I would like to keep. – itslittlejohn Dec 19 '13 at 17:42
  • 2
    @itslittlejohn The problem isn't the `$` by itself, it is `$id` as a whole. Json.Net uses this notation (along with `$ref`) for its own purposes to preserve object references in JSON. So when it sees `$id` it assumes that property is not part of the actual JSON property set, but an internal identifier. Doing a string replace of `"$id"` with `"id"` (including the quote characters) before deserializing could be a viable workaround. This character sequence is unlikely to be elsewhere in your actual response body. If I have more time, I'll look into this more and see if there's a better solution. – Brian Rogers Dec 19 '13 at 19:08
  • @BrianRogers Thanks Brian. I'd be grateful to know if you are aware of a good way to handle this. I imagine that the string replace would work, but I don't want to have to do that for every single response from the server. – itslittlejohn Dec 19 '13 at 21:24
0

This answer fixed the $id/$ref problem for me: Json.Net adding $id to EF objects despite setting PreserveReferencesHandling to "None"

In your implementation of DefaultContractResolver/IContractResolver, add this;

public override JsonContract ResolveContract(Type type) {
    var contract = base.ResolveContract(type);
    contract.IsReference = false;
    return contract;
}

EDIT: This will remove the $id.

Community
  • 1
  • 1
Howie
  • 2,760
  • 6
  • 32
  • 60
  • 2
    This answer solves a different problem than was asked in the question. The question was how to get the `$id` value from the JSON during deserialization. Your answer tells how to remove the `$id` value during serialization. – Brian Rogers Mar 11 '14 at 14:06