2013-10-10 15 views
46

Imgur api cuộc gọi này trả về một danh sách có chứa cả Gallery Hình ảnhGallery Album lớp đại diện trong json.deserializing lớp json đa hình mà không cần loại thông tin sử dụng json.net

Tôi không thể xem cách tự động hóa các tự động này bằng cách sử dụng Json.NET cho rằng không có thuộc tính $ type nào nói với deserializer lớp nào được biểu diễn. Có một thuộc tính được gọi là "IsAlbum" có thể được sử dụng để phân biệt giữa hai.

This câu hỏi xuất hiện để hiển thị một phương pháp nhưng có vẻ như hơi bị hack.

Tôi làm cách nào để loại bỏ các lớp này? (sử dụng C#, Json.NET).

mẫu dữ liệu:

Gallery Hình ảnh

{ 
    "id": "OUHDm", 
    "title": "My most recent drawing. Spent over 100 hours.", 
     ... 
    "is_album": false 
} 

Gallery Album

{ 
    "id": "lDRB2", 
    "title": "Imgur Office", 
    ... 
    "is_album": true, 
    "images_count": 3, 
    "images": [ 
     { 
      "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" 
     } 
    ] 
} 

}

+0

Bạn muốn lấy chuỗi Json và đặt nó vào lớp học? Và tôi đang bối rối bởi những gì bạn có nghĩa là bởi 'không có $ loại tài sản'. – gunr2171

+0

Có, tôi có chuỗi json và muốn deserialize C# lớp học. Json.NET xuất hiện để sử dụng một thuộc tính được gọi là $ type để vẽ một sự phân biệt giữa các kiểu khác nhau được giữ trong một mảng. Dữ liệu này không có thuộc tính đó và chỉ sử dụng thuộc tính 'IsAlbum'. –

Trả lời

77

Bạn có thể làm được điều này khá dễ dàng bằng cách tạo ra một c ustom JsonConverter để xử lý việc khởi tạo đối tượng. Giả sử bạn đã lớp học của bạn được xác định một cái gì đó như thế này:

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<GalleryImage> images { get; set; } 
} 

Bạn sẽ tạo ra bộ chuyển đổi như thế này:

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<bool>()) 
     { 
      return item.ToObject<GalleryAlbum>(); 
     } 
     else 
     { 
      return item.ToObject<GalleryImage>(); 
     } 
    } 

    public override void WriteJson(JsonWriter writer, 
     object value, JsonSerializer serializer) 
    { 
     throw new NotImplementedException(); 
    } 
} 

Dưới đây là một chương trình ví dụ cho thấy sự chuyển đổi trong hành động:

class Program 
{ 
    static void Main(string[] args) 
    { 
     string json = @" 
     [ 
      { 
       ""id"": ""OUHDm"", 
       ""title"": ""My most recent drawing. Spent over 100 hours."", 
       ""link"": ""http://i.imgur.com/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://i.imgur.com/24nLu.jpg"" 
        }, 
        { 
         ""id"": ""Ziz25"", 
         ""link"": ""http://i.imgur.com/Ziz25.jpg"" 
        }, 
        { 
         ""id"": ""9tzW6"", 
         ""link"": ""http://i.imgur.com/9tzW6.jpg"" 
        } 
       ] 
      } 
     ]"; 

     List<GalleryItem> items = 
      JsonConvert.DeserializeObject<List<GalleryItem>>(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(); 
     } 
    } 
} 

Và đây là kết quả của chương trình trên:

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 
+11

Điều này không hoạt động nếu các đối tượng đa hình được đệ quy, tức là nếu một Album có thể chứa các anbom khác. Trong trình biến đổi nên sử dụng Serializer.Populate() thay vì item.ToObject(). Xem http://stackoverflow.com/questions/29124126/polymorphic-json-deserialization-failing-using-json-net –

+0

Tuyệt vời, điều này làm việc tuyệt vời! –

+0

Đối với bất kỳ ai thử phương pháp này và tìm kết quả trong một vòng lặp vô hạn (và cuối cùng là tràn ngăn xếp), bạn có thể muốn sử dụng phương thức 'Populate' thay vì' ToObject'. Xem câu trả lời cho https://stackoverflow.com/questions/25404202/custom-inheritance-jsonconverter-fails-when-jsonconverterattribute-is-used và https://stackoverflow.com/questions/29124126/polymorphic-json-deserialization- không sử dụng-json-net. Tôi có một ví dụ về hai cách tiếp cận trong Gist tại đây: https://gist.github.com/chrisoldwood/b604d69543a5fe5896a94409058c7a95. –

0

Tôi chỉ đăng nội dung này để xóa một số nhầm lẫn. Nếu bạn đang làm việc với một định dạng được xác định trước và cần phải deserialize nó, đây là những gì tôi thấy làm việc tốt nhất và chứng minh các cơ chế để những người khác có thể tinh chỉnh nó khi cần thiết.

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<string, Type> 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<BaseClass>(reader); 
     } 
    } 
+0

Làm thế nào để bạn sử dụng điều này khi sử dụng chuyển đổi ngầm định mà không sử dụng thuộc tính [JsonConverter()] (được nhận xét là "quan trọng")? Ví dụ: deserializing thông qua thuộc tính '[FromBody]'? –

+1

Tôi cho rằng bạn chỉ cần chỉnh sửa cài đặt JsonFormatter toàn cầu để bao gồm công cụ chuyển đổi này. Xem https://stackoverflow.com/questions/41629523/using-a-custom-json-formatter-for-web-api-2 – xtravar

+0

Oh rực rỡ - cảm ơn bạn. –

1

thực hiện sau nên để bạn de-serialize mà không thay đổi cách bạn đã thiết kế lớp học của bạn và bằng cách sử dụng một lĩnh vực khác hơn $ loại để quyết định những gì để de-serialize nó vào.

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<Func<GalleryImage, bool>>)(s => s.is_album)).Body).Member.Name; 
      JToken jt; 
      if (!jo.TryGetValue(isAlbumPropertyName, StringComparison.InvariantCultureIgnoreCase, out jt)) 
      { 
       return jo.ToObject<GalleryImage>(); 
      } 
      var propValue = jt.Value<bool>(); 
      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(); 
    } 
} 
0

Đơn giản chỉ cần với JsonSubTypes thuộc tính mà làm việc với 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<GalleryImage> images { get; set; } 
    } 
Các vấn đề liên quan