Удаление десериализации полиморфных classов json без информации о типе с использованием json.net
Этот вызов Imgur api возвращает список, содержащий classы Gallery Image и Gallery Album, представленные в json.
Я не вижу, как их десериализовать автоматически, используя Json.NET, учитывая, что свойство $ type не указывает десериализатор, который должен быть представлен. Существует свойство «IsAlbum», которое можно использовать для разграничения между ними.
Этот вопрос, кажется, показывает один метод, но он выглядит немного взломанным.
- Невозможно десериализовать lambda
- ShouldSerialize * () vs * Условный шаблон условной сериализации
- Сериализация classа, который содержит std :: string
- Django rest framework, используйте разные сериализаторы в том же ModelViewSet
- Сериализация JSON массива с полиморфными объектами
Как мне заняться десериализацией этих classов? (используя C #, Json.NET) .
Пример данных:
Изображение галереи
{ "id": "OUHDm", "title": "My most recent drawing. Spent over 100 hours.", ... "is_album": false }
Альбом галереи
{ "id": "lDRB2", "title": "Imgur Office", ... "is_album": true, "images_count": 3, "images": [ { "id": "24nLu", ... "link": "http://sofru.miximages.com/c%23/24nLu.jpg" }, { "id": "Ziz25", ... "link": "http://sofru.miximages.com/c%23/Ziz25.jpg" }, { "id": "9tzW6", ... "link": "http://sofru.miximages.com/c%23/9tzW6.jpg" } ] }
}
- StackOverflowError при сериализации объекта в Java
- Уничтожить stream массива json по одному элементу за раз
- Сериализация нескольких свойств DateTime в одном classе с использованием разных форматов для каждого из них
- EF 4.1 - Code First - Ошибка серийной ссылки JSON
- Специальная сериализация Jackson JSON для определенных полей
- Как реализовать TypeAdapterFactory в Gson?
- Как вы делаете глубокую копию объекта в .NET (например, C #)?
- ТипNameHandling предостережение в Newtonsoft Json
Вы можете сделать это довольно легко, создав пользовательский JsonConverter
для обработки экземпляра объекта. Предполагая, что у вас есть classы, что-то вроде этого:
public abstract class GalleryItem { public string id { get; set; } public string title { get; set; } public string link { get; set; } public bool is_album { get; set; } } public class GalleryImage : GalleryItem { // ... } public class GalleryAlbum : GalleryItem { public int images_count { get; set; } public List images { get; set; } }
Вы бы создали такой конвертер:
public class GalleryItemConverter : JsonConverter { public override bool CanConvert(Type objectType) { return typeof(GalleryItem).IsAssignableFrom(objectType); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject item = JObject.Load(reader); if (item["is_album"].Value()) { return item.ToObject(); } else { return item.ToObject(); } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Вот пример программы, показывающей конвертер в действии:
class Program { static void Main(string[] args) { string json = @" [ { ""id"": ""OUHDm"", ""title"": ""My most recent drawing. Spent over 100 hours."", ""link"": ""http://sofru.miximages.com/c%23/OUHDm.jpg"", ""is_album"": false }, { ""id"": ""lDRB2"", ""title"": ""Imgur Office"", ""link"": ""http://alanbox.imgur.com/a/lDRB2"", ""is_album"": true, ""images_count"": 3, ""images"": [ { ""id"": ""24nLu"", ""link"": ""http://sofru.miximages.com/c%23/24nLu.jpg"" }, { ""id"": ""Ziz25"", ""link"": ""http://sofru.miximages.com/c%23/Ziz25.jpg"" }, { ""id"": ""9tzW6"", ""link"": ""http://sofru.miximages.com/c%23/9tzW6.jpg"" } ] } ]"; List items = JsonConvert.DeserializeObject>(json, new GalleryItemConverter()); foreach (GalleryItem item in items) { Console.WriteLine("id: " + item.id); Console.WriteLine("title: " + item.title); Console.WriteLine("link: " + item.link); if (item.is_album) { GalleryAlbum album = (GalleryAlbum)item; Console.WriteLine("album images (" + album.images_count + "):"); foreach (GalleryImage image in album.images) { Console.WriteLine(" id: " + image.id); Console.WriteLine(" link: " + image.link); } } Console.WriteLine(); } } }
И вот вывод вышеупомянутой программы:
id: OUHDm title: My most recent drawing. Spent over 100 hours. link: http://i.imgur.com/OUHDm.jpg id: lDRB2 title: Imgur Office link: http://alanbox.imgur.com/a/lDRB2 album images (3): id: 24nLu link: http://i.imgur.com/24nLu.jpg id: Ziz25 link: http://i.imgur.com/Ziz25.jpg id: 9tzW6 link: http://i.imgur.com/9tzW6.jpg
Просто с атрибутами JsonSubTypes, которые работают с Json.NET
[JsonConverter(typeof(JsonSubtypes), "is_album")] [JsonSubtypes.KnownSubType(typeof(GalleryAlbum), true)] [JsonSubtypes.KnownSubType(typeof(GalleryImage), false)] public abstract class GalleryItem { public string id { get; set; } public string title { get; set; } public string link { get; set; } public bool is_album { get; set; } } public class GalleryImage : GalleryItem { // ... } public class GalleryAlbum : GalleryItem { public int images_count { get; set; } public List images { get; set; } }
После выполнения следует разрешить сериализацию без изменения способа разработки ваших classов и с помощью поля, отличного от $ type, для принятия решения о его де-сериализации.
public class GalleryImageConverter : JsonConverter { public override bool CanConvert(Type objectType) { return (objectType == typeof(GalleryImage) || objectType == typeof(GalleryAlbum)); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { try { if (!CanConvert(objectType)) throw new InvalidDataException("Invalid type of object"); JObject jo = JObject.Load(reader); // following is to avoid use of magic strings var isAlbumPropertyName = ((MemberExpression)((Expression>)(s => s.is_album)).Body).Member.Name; JToken jt; if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt)) { return jo.ToObject(); } var propValue = jt.Value(); if(propValue) { resultType = typeof(GalleryAlbum); } else{ resultType = typeof(GalleryImage); } var resultObject = Convert.ChangeType(Activator.CreateInstance(resultType), resultType); var objectProperties=resultType.GetProperties(); foreach (var objectProperty in objectProperties) { var propType = objectProperty.PropertyType; var propName = objectProperty.Name; var token = jo.GetValue(propName, StringComparison.InvariantCultureIgnoreCase); if (token != null) { objectProperty.SetValue(resultObject,token.ToObject(propType)?? objectProperty.GetValue(resultObject)); } } return resultObject; } catch (Exception ex) { throw; } } public override bool CanWrite { get { return false; } } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); } }
Я только публикую это, чтобы прояснить некоторые путаницы. Если вы работаете с предопределенным форматом и нуждаетесь в десериализации, это то, что я нашел лучше всего работающим и демонстрирует механику, чтобы другие могли настраивать ее по мере необходимости.
public class BaseClassConverter : JsonConverter { public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var j = JObject.Load(reader); var retval = BaseClass.From(j, serializer); return retval; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { serializer.Serialize(writer, value); } public override bool CanConvert(Type objectType) { // important - do not cause subclasses to go through this converter return objectType == typeof(BaseClass); } } // important to not use attribute otherwise you'll infinite loop public abstract class BaseClass { internal static Type[] Types = new Type[] { typeof(Subclass1), typeof(Subclass2), typeof(Subclass3) }; internal static Dictionary TypesByName = Types.ToDictionary(t => t.Name.Split('.').Last()); // type property based off of class name [JsonProperty(PropertyName = "type", Required = Required.Always)] public string JsonObjectType { get { return this.GetType().Name.Split('.').Last(); } set { } } // convenience method to deserialize a JObject public static new BaseClass From(JObject obj, JsonSerializer serializer) { // this is our object type property var str = (string)obj["type"]; // we map using a dictionary, but you can do whatever you want var type = TypesByName[str]; // important to pass serializer (and its settings) along return obj.ToObject(type, serializer) as BaseClass; } // convenience method for deserialization public static BaseClass Deserialize(JsonReader reader) { JsonSerializer ser = new JsonSerializer(); // important to add converter here ser.Converters.Add(new BaseClassConverter()); return ser.Deserialize(reader); } }