Можно ли указать путь в атрибуте для сопоставления свойства в моем classе с дочерним свойством в моем JSON?

Существует некоторый код (который я не могу изменить), который использует Newtonsoft.Json’s DeserializeObject(strJSONData) для получения данных из веб-запроса и преобразования его в объект classа (я могу изменить class). [DataMember(Name = "raw_property_name")] свойства моего classа с помощью [DataMember(Name = "raw_property_name")] я могу сопоставить необработанные данные JSON с правильным свойством в моем classе. Есть ли способ сопоставить дочернее свойство сложного объекта JSON с простым свойством? Вот пример:

 { "picture": { "id": 123456, "data": { "type": "jpg", "url": "http://www.someplace.com/mypicture.jpg" } } } 

Я не забочусь ни о каком остальном объекте картинки, кроме URL-адреса, и поэтому не хочу настраивать сложный объект в моем classе C #. Я просто хочу что-то вроде:

 [DataMember(Name = "picture.data.url")] public string ProfilePicture { get; set; } 

Это возможно?

Ну, если вам просто нужно одно дополнительное свойство, один простой подход – разобрать JSON на JObject , использовать ToObject() чтобы заполнить ваш class из JObject , а затем использовать SelectToken() чтобы SelectToken() дополнительное свойство.

Итак, если ваш class выглядит примерно так:

 class Person { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("age")] public string Age { get; set; } public string ProfilePicture { get; set; } } 

Вы можете сделать это:

 string json = @" { ""name"" : ""Joe Shmoe"", ""age"" : 26, ""picture"": { ""id"": 123456, ""data"": { ""type"": ""jpg"", ""url"": ""http://www.someplace.com/mypicture.jpg"" } } }"; JObject jo = JObject.Parse(json); Person p = jo.ToObject(); p.ProfilePicture = (string)jo.SelectToken("picture.data.url"); 

Скрипт: https://dotnetfiddle.net/7gnJCK


Если вы предпочитаете более привлекательное решение, вы можете создать пользовательский JsonConverter чтобы атрибут JsonProperty мог вести себя так, как вы описываете. Конвертер должен работать на уровне classа и использовать некоторое reflection в сочетании с вышеупомянутой техникой для заполнения всех свойств. Вот как это выглядит в коде:

 class JsonPathConverter : JsonConverter { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); object targetObj = Activator.CreateInstance(objectType); foreach (PropertyInfo prop in objectType.GetProperties() .Where(p => p.CanRead && p.CanWrite)) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType() .FirstOrDefault(); string jsonPath = (att != null ? att.PropertyName : prop.Name); JToken token = jo.SelectToken(jsonPath); if (token != null && token.Type != JTokenType.Null) { object value = token.ToObject(prop.PropertyType, serializer); prop.SetValue(targetObj, value, null); } } return targetObj; } public override bool CanConvert(Type objectType) { // CanConvert is not called when [JsonConverter] attribute is used return false; } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } } 

Чтобы продемонстрировать, предположим, что JSON теперь выглядит следующим образом:

 { "name": "Joe Shmoe", "age": 26, "picture": { "id": 123456, "data": { "type": "jpg", "url": "http://www.someplace.com/mypicture.jpg" } }, "favorites": { "movie": { "title": "The Godfather", "starring": "Marlon Brando", "year": 1972 }, "color": "purple" } } 

… и вас интересует любимый фильм (название и год) человека и любимый цвет в дополнение к информации, полученной ранее. Сначала вы должны отметить свой целевой class атрибутом [JsonConverter] чтобы связать его с пользовательским конвертером, а затем использовать [JsonProperty] для каждого свойства, указав желаемый путь свойства (с учетом регистра) в качестве имени. Целевые свойства также не должны быть примитивами: вы можете использовать дочерний class, как я здесь, с Movie (и обратите внимание, что не требуется промежуточного classа Favorites ).

 [JsonConverter(typeof(JsonPathConverter))] class Person { [JsonProperty("name")] public string Name { get; set; } [JsonProperty("age")] public int Age { get; set; } [JsonProperty("picture.data.url")] public string ProfilePicture { get; set; } [JsonProperty("favorites.movie")] public Movie FavoriteMovie { get; set; } [JsonProperty("favorites.color")] public string FavoriteColor { get; set; } } // Don't need to mark up these properties because they are covered by the // property paths in the Person class class Movie { public string Title { get; set; } public int Year { get; set; } } 

Со всеми атрибутами на месте вы можете просто десериализовать как обычно, и он должен «просто работать»:

 Person p = JsonConvert.DeserializeObject(json); 

Сценарий: https://dotnetfiddle.net/Ljw32O

Отмеченный ответ не выполнен на 100%, поскольку он игнорирует любой IContractResolver, который может быть зарегистрирован, например CamelCasePropertyNamesContractResolver и т. Д.

Также возвращать false для can convert может предотвратить другие пользовательские случаи, поэтому я изменил его, чтобы return objectType.GetCustomAttributes(true).OfType().Any();

Вот обновленная версия: https://dotnetfiddle.net/F8C8U8

Я также удалил необходимость установки JsonProperty на свойство, как показано в ссылке.

Если по какой-либо причине ссылка выше умирает или взрывается, я также включаю следующий код:

 public class JsonPathConverter : JsonConverter { ///  public override object ReadJson( JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject jo = JObject.Load(reader); object targetObj = Activator.CreateInstance(objectType); foreach (PropertyInfo prop in objectType.GetProperties().Where(p => p.CanRead && p.CanWrite)) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType() .FirstOrDefault(); string jsonPath = att != null ? att.PropertyName : prop.Name; if (serializer.ContractResolver is DefaultContractResolver) { var resolver = (DefaultContractResolver)serializer.ContractResolver; jsonPath = resolver.GetResolvedPropertyName(jsonPath); } if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) { throw new InvalidOperationException($"JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots but name was ${jsonPath}."); // Array operations not permitted } JToken token = jo.SelectToken(jsonPath); if (token != null && token.Type != JTokenType.Null) { object value = token.ToObject(prop.PropertyType, serializer); prop.SetValue(targetObj, value, null); } } return targetObj; } ///  public override bool CanConvert(Type objectType) { // CanConvert is not called when [JsonConverter] attribute is used return objectType.GetCustomAttributes(true).OfType().Any(); } ///  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var properties = value.GetType().GetRuntimeProperties().Where(p => p.CanRead && p.CanWrite); JObject main = new JObject(); foreach (PropertyInfo prop in properties) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType() .FirstOrDefault(); string jsonPath = att != null ? att.PropertyName : prop.Name; if (serializer.ContractResolver is DefaultContractResolver) { var resolver = (DefaultContractResolver)serializer.ContractResolver; jsonPath = resolver.GetResolvedPropertyName(jsonPath); } var nesting = jsonPath.Split('.'); JObject lastLevel = main; for (int i = 0; i < nesting.Length; i++) { if (i == nesting.Length - 1) { lastLevel[nesting[i]] = new JValue(prop.GetValue(value)); } else { if (lastLevel[nesting[i]] == null) { lastLevel[nesting[i]] = new JObject(); } lastLevel = (JObject)lastLevel[nesting[i]]; } } } serializer.Serialize(writer, main); } } 

Идти

 lastLevel [nesting [i]] = new JValue(prop.GetValue (value)); 

Ты должен сделать

 lastLevel[nesting[i]] = JValue.FromObject(jValue); 

В противном случае мы имеем

Не удалось определить тип объекта JSON для типа …

исключение

Если кому-то нужно использовать JsonPathConverter @BrianRogers также с параметром WriteJson , вот решение (которое работает только для путей только с точками ):

Удалите свойство CanWrite чтобы оно снова стало true по умолчанию.

Замените код WriteJson следующим:

 public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { var properties = value.GetType().GetRuntimeProperties ().Where(p => p.CanRead && p.CanWrite); JObject main = new JObject (); foreach (PropertyInfo prop in properties) { JsonPropertyAttribute att = prop.GetCustomAttributes(true) .OfType() .FirstOrDefault(); string jsonPath = (att != null ? att.PropertyName : prop.Name); var nesting=jsonPath.Split(new[] { '.' }); JObject lastLevel = main; for (int i = 0; i < nesting.Length; i++) { if (i == nesting.Length - 1) { lastLevel [nesting [i]] = new JValue(prop.GetValue (value)); } else { if (lastLevel [nesting [i]] == null) { lastLevel [nesting [i]] = new JObject (); } lastLevel = (JObject)lastLevel [nesting [i]]; } } } serializer.Serialize (writer, main); } 

Как я сказал выше, это работает только для путей, содержащих точки . Учитывая это, вы должны добавить следующий код в ReadJson , чтобы предотвратить другие случаи:

 [...] string jsonPath = (att != null ? att.PropertyName : prop.Name); if (!Regex.IsMatch(jsonPath, @"^[a-zA-Z0-9_.-]+$")) { throw new InvalidOperationException("JProperties of JsonPathConverter can have only letters, numbers, underscores, hiffens and dots."); //Array operations not permitted } JToken token = jo.SelectToken(jsonPath); [...] 
  • Удаление десериализации JSON в .NET-объект с использованием Newtonsoft (или LINQ to JSON, возможно?)
  • Разбор большого json-файла в .NET.
  • VB.net JSON Deserialize
  • Десериализация JSON с использованием JSon.NET с динамическими данными
  • Json.NET: десериализация вложенных словарей
  • Newtonsoft JSON Deserialize
  • Преобразование int для bool с Json.Net
  • Сериализация - readObject writeObject переопределяет
  • Отключить JSON в существующий объект (Java)
  • Json.NET Отключить десериализацию в DateTime
  • Давайте будем гением компьютера.