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.