JsonConvert.DeserializeObject (string) возвращает значение null для свойства $id

я загружаю JSON с помощью системы.Сеть.WebClient.DownloadString. Я получаю действительный ответ:

{
"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"
    }]
}

и вот моя модель:

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

все работает, когда я делаю:

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

за исключением того, что свойство Id ($id) всегда равно null. Сначала я пытался понять, отличается ли символ доллара, который я получал с сервера, но, похоже, это не так. Я не уверен, куда идти отсюда, так что любой идеи?

спасибо заранее.

Примечание: Если я пытаюсь десериализоваться с чем-то вроде JavaScriptSerializer, он работает отлично, поэтому я уверен, что это что-то не так с моей моделью или с JSON.net - ... Хотя, возможно, ошибаешься.

3 ответов


Json.Net обычно использует $id вместе с $ref как метаданные для сохранения ссылок на объекты в JSON. Поэтому, когда он видит $id предполагается, что свойство не является частью фактического набора свойств JSON, а внутренним идентификатором. Таким образом, он не заполняет Id свойство на вашем объекте, даже если вы включили [JsonProperty] атрибут, указывающий, что он должен.

обновление

по состоянию на Json.Net версия 6.0.4 есть новый параметр, с помощью которого десериализатор может обрабатывать эти свойства "метаданных" как обычные свойства, а не использовать их. Все, что вам нужно сделать, это установить MetadataPropertyHandling параметр Ignore а затем десериализоваться, как обычно.

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

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

до версии 6.0.4 для решения этой проблемы требовалось обходное решение. В остальной части этого ответа обсуждаются возможные обходные пути. Если вы используете 6.0.4 или более поздней версии, вам не нужно обходной путь и может остановить чтение сейчас.


самый простой обходной путь, который я вижу, - это сделать замену строки "$id" С "id" (включая кавычки) на JSON до десериализации, как предложил @Carlos Coelho. Поскольку вам придется делать это с каждым ответом, если вы идете по этому маршруту, я бы рекомендовал сделать простой вспомогательный метод, чтобы избежать дублирования кода, например:

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

однако, так как вы сказали в своих комментариях, что вы не так заинтересованы в идее использования строки замените, я посмотрел другие варианты. Я нашел еще одну альтернативу, которая может сработать для вас-обычай JsonConverter. Идея конвертера заключается в том, что он попытается использовать Json.Net встроенные механизмы десериализации для создания и заполнения объекта (без идентификатора sans), а затем вручную получить $id свойство из JSON и использовать его для заполнения Id свойство объекта через отражение.

вот код для преобразователя:

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

я пытался напишите конвертер так, чтобы он работал для любого объекта, который имеет $id -- вам просто нужно изменить CanConvert метод соответственно, так что он возвращает true для всех типов, которые вам нужно использовать в дополнение к FormDefinition.

чтобы использовать конвертер, вам просто нужно передать его экземпляр в DeserializeObject<T> такой:

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

важное замечание: у вас может возникнуть соблазн украсить свои классы JsonConverter атрибут вместо передачи преобразователь в DeserializeObject вызов, но не делайте этого-это заставит преобразователь перейти в рекурсивный цикл, пока стек не переполнится. (Существует способ заставить конвертер работать с атрибутом, но вам придется переписать ReadJson метод для ручного создания целевого объекта и заполнения его свойств вместо вызова jo.ToObject(objectType). Это выполнимо, но немного сумбурно.)

Дайте мне знать, если это работает для вас.


проблема заключается в знаке$, поэтому обходным путем будет:

удалите $ из аннотации JsonProperty.

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

в коде замените специальный символ $

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

отредактировано как @BrianRogers предлагает


этот ответ исправил проблему $id/$ref для меня:Json.Net добавление $id в объекты EF, несмотря на установку PreserveReferencesHandling в "None"

в вашей реализации DefaultContractResolver/IContractResolver добавьте это;

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

EDIT: это удалит $id.