Можно ли указать путь в атрибуте для сопоставления свойства в моем 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); [...] 
  • Преобразование int для bool с Json.Net
  • Уничтожить XML-объект с помощью динамического
  • Удаление десериализации JSON в .NET-объект с использованием Newtonsoft (или LINQ to JSON, возможно?)
  • JSON для экземпляра classа TypeScript?
  • Дессериализация данных JSON на C # с использованием JSON.NET
  • Уничтожить вложенные JSON в объекты C #
  • Newtonsoft JSON Deserialize
  • VB.net JSON Deserialize
  • Json.NET Отключить десериализацию в DateTime
  • Дезерминирование полиморфных типов с jacksonом
  • Interesting Posts

    Есть ли команда linux, например mv, но с регулярным выражением?

    неизвестная ошибка: результат функции вызова отсутствует «значение» для Selenium Send Keys даже после обновления хромированной передачи

    Как дождаться завершения ряда streamов?

    Эквивалент фильтра содержимого K9 для Ubuntu

    Полиморфизм. Является ли объект ORM субъектом домена или объектом данных?

    Как я могу поместить int с ведущими нулями при использовании оператора cout <<?

    Как запустить meteorитный сервер на другом IP-адресе?

    проверка хотя бы одного флажка

    Полнотекстовый поиск в Android

    Лучшая практика: 301 Перенаправление HTTP на HTTPS (стандартный домен)

    динамический массив с двумя размерами в соответствии с вводом

    Чтение данных из файла Excel в Objective-C (iPhone)

    Я забыл пароль своего хранилища ключей, и я хочу установить apk в Google Play. Что я делаю?

    Порядок методов расширения LINQ не влияет на производительность?

    Пространства и скобки в переменных Windows PATH закручивают пакетные файлы

    Давайте будем гением компьютера.