Sử dụng bộ chuyển đổi Json.NET để giải mã các thuộc tính


88

Tôi có một định nghĩa lớp có chứa một thuộc tính trả về một giao diện.

public class Foo
{ 
    public int Number { get; set; }

    public ISomething Thing { get; set; }
}

Cố gắng tuần tự hóa lớp Foo bằng Json.NET cho tôi một thông báo lỗi như "Không thể tạo một phiên bản kiểu 'ISomething'. Một thứ gì đó có thể là một giao diện hoặc lớp trừu tượng."

Có thuộc tính hoặc bộ chuyển đổi Json.NET nào cho phép tôi chỉ định một Somethinglớp cụ thể để sử dụng trong quá trình giải mã không?


Tôi tin rằng bạn cần chỉ định tên thuộc tính lấy / đặt ISomething
ram

Tôi có. Tôi đang sử dụng cách viết tắt cho các thuộc tính được triển khai tự động được giới thiệu trong C # 3.5. msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher

4
Không phải là một cái gì đó là loại. Tôi nghĩ ram là đúng, bạn vẫn cần một tên thuộc tính. Tôi biết điều này không liên quan đến vấn đề của bạn, nhưng nhận xét của bạn ở trên khiến tôi nghĩ rằng tôi đã thiếu một số tính năng mới trong .NET cho phép bạn chỉ định một thuộc tính mà không có tên.
Mr Moose

Câu trả lời:


92

Một trong những điều bạn có thể làm với Json.NET là:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;

JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Các TypeNameHandlinglá cờ sẽ thêm một $typetài sản cho JSON, cho phép Json.NET để biết được cụ thể loại nó cần phải deserialize đối tượng vào. Điều này cho phép bạn giải mã hóa một đối tượng trong khi vẫn hoàn thành một giao diện hoặc lớp cơ sở trừu tượng.

Tuy nhiên, nhược điểm là điều này rất đặc trưng cho Json.NET. Đây $typesẽ là một loại hoàn toàn đủ điều kiện, vì vậy nếu bạn đang tuần tự hóa nó với thông tin loại, thì bộ giải mã cũng cần phải hiểu nó.

Tài liệu: Cài đặt tuần tự hóa với Json.NET


Hấp dẫn. Tôi sẽ phải giải quyết vấn đề này. Mẹo hay!
dthrasher

2
Đối với Newtonsoft.Json, nó hoạt động tương tự, nhưng thuộc tính là "$ type"
Jaap

Điều đó quá dễ dàng!
Shimmy Weitzhandler

1
Chú ý các vấn đề bảo mật có thể xảy ra tại đây khi sử dụng TypeNameHandling. Xem phần thận trọng TypeNameHandling trong Newtonsoft Json để biết thêm chi tiết.
dbc

Tôi đã vật lộn như điên với bộ chuyển đổi ngày hôm qua, và đây là cách tốt hơn và dễ hiểu hơn, cảm ơn !!!
Horothenic

52

Bạn có thể đạt được điều này thông qua việc sử dụng lớp JsonConverter. Giả sử bạn có một lớp có thuộc tính giao diện;

public class Organisation {
  public string Name { get; set; }

  [JsonConverter(typeof(TycoonConverter))]
  public IPerson Owner { get; set; }
}

public interface IPerson {
  string Name { get; set; }
}

public class Tycoon : IPerson {
  public string Name { get; set; }
}

JsonConverter của bạn chịu trách nhiệm tuần tự hóa và hủy tuần tự hóa thuộc tính cơ bản;

public class TycoonConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return (objectType == typeof(IPerson));
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    return serializer.Deserialize<Tycoon>(reader);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // Left as an exercise to the reader :)
    throw new NotImplementedException();
  }
}

Khi bạn làm việc với Tổ chức được deserialized qua Json.Net, IPerson cơ bản cho thuộc tính Owner sẽ thuộc loại Tycoon.


Rất đẹp. Tôi sẽ phải thử bộ chuyển đổi.
dthrasher

4
Thẻ "[JsonConverter (typeof (TycoonConverter))]" có còn hoạt động nếu nó nằm trong danh sách giao diện không?
Zwik

40

Thay vì chuyển một đối tượng JsonSerializerSettings tùy chỉnh đến JsonConvert.SerializeObject () với tùy chọn TypeNameHandling.Objects, như đã đề cập trước đây, bạn chỉ có thể đánh dấu thuộc tính giao diện cụ thể đó bằng một thuộc tính để JSON được tạo sẽ không bị cồng kềnh với thuộc tính "$ type" trên MỌI đối tượng:

public class Foo
{
    public int Number { get; set; }

    // Add "$type" property containing type info of concrete class.
    [JsonProperty( TypeNameHandling = TypeNameHandling.Objects )]
    public ISomething { get; set; }
}

Xuất sắc. Cảm ơn :)
Darren Young

5
Đối với tập hợp các giao diện hoặc các lớp trừu tượng, thuộc tính là "ItemTypeNameHandling". ví dụ: [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

Cảm ơn vì điều này!
brudert

23

Trong phiên bản mới nhất của trình chuyển đổi Newtonsoft Json của bên thứ ba, bạn có thể đặt một hàm tạo với kiểu cụ thể liên quan đến thuộc tính được giao diện.

public class Foo
{ 
    public int Number { get; private set; }

    public ISomething IsSomething { get; private set; }

    public Foo(int number, Something concreteType)
    {
        Number = number;
        IsSomething = concreteType;
    }
}

Miễn là Cái gì đó thực hiện thì điều này sẽ hoạt động. Cũng không đặt một hàm tạo trống mặc định trong trường hợp trình chuyển đổi JSon cố gắng sử dụng nó, bạn phải buộc nó sử dụng hàm tạo chứa kiểu cụ thể.

Tái bút. điều này cũng cho phép bạn đặt bộ cài đặt của mình ở chế độ riêng tư.


6
Điều này nên được hét lên từ các mái nhà! Đúng, nó thêm những ràng buộc đối với việc triển khai cụ thể, nhưng nó đơn giản hơn rất nhiều so với các phương pháp tiếp cận khác cho những trường hợp có thể sử dụng nó.
Mark Meuer

3
Điều gì sẽ xảy ra nếu chúng ta có nhiều hơn 1 người thi công với nhiều loại bê tông, nó vẫn sẽ biết?
Teoman shipahi

1
Câu trả lời này rất thanh lịch so với tất cả những điều vô nghĩa phức tạp mà bạn phải làm khác. Đây phải là câu trả lời được chấp nhận. Tuy nhiên, một lưu ý trong trường hợp của tôi là tôi phải thêm [JsonConstructor] trước hàm khởi tạo để nó hoạt động .... Tôi nghi ngờ rằng việc sử dụng điều này trên chỉ MỘT trong số các bộ xây dựng bê tông của bạn sẽ giải quyết được vấn đề (4 tuổi) của bạn @Teomanshipahi
nacitar sevaht 11/118

@nacitarsevaht Tôi có thể quay lại và khắc phục sự cố của mình ngay bây giờ :) dù sao thì tôi thậm chí không nhớ nó là gì, nhưng khi tôi nhìn lại thì đây là một giải pháp tốt cho một số trường hợp nhất định.
Teoman shipahi

chúng tôi cũng sử dụng điều này nhưng tôi thích chuyển đổi trong hầu hết các trường hợp vì việc kết hợp loại cụ thể với hàm tạo sẽ làm mất đi mục đích của việc sử dụng giao diện cho thuộc tính ngay từ đầu!
gabe

19

Gặp phải vấn đề tương tự vì vậy tôi đã nghĩ ra Bộ chuyển đổi của riêng mình sử dụng đối số kiểu đã biết.

public class JsonKnownTypeConverter : JsonConverter
{
    public IEnumerable<Type> KnownTypes { get; set; }

    public JsonKnownTypeConverter(IEnumerable<Type> knownTypes)
    {
        KnownTypes = knownTypes;
    }

    protected object Create(Type objectType, JObject jObject)
    {
        if (jObject["$type"] != null)
        {
            string typeName = jObject["$type"].ToString();
            return Activator.CreateInstance(KnownTypes.First(x =>typeName.Contains("."+x.Name+",")));
        }

        throw new InvalidOperationException("No supported type");
    }

    public override bool CanConvert(Type objectType)
    {
        if (KnownTypes == null)
            return false;

        return (objectType.IsInterface || objectType.IsAbstract) && KnownTypes.Any(objectType.IsAssignableFrom);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // Load JObject from stream
        JObject jObject = JObject.Load(reader);
        // Create target object based on JObject
        var target = Create(objectType, jObject);
        // Populate the object properties
        serializer.Populate(jObject.CreateReader(), target);
        return target;
    }

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

Tôi đã xác định hai phương thức mở rộng để giải không khí và tuần tự hóa:

public static class AltiJsonSerializer
{
    public static T DeserializeJson<T>(this string jsonString, IEnumerable<Type> knownTypes = null)
    {
        if (string.IsNullOrEmpty(jsonString))
            return default(T);

        return JsonConvert.DeserializeObject<T>(jsonString,
                new JsonSerializerSettings
                {
                    TypeNameHandling = TypeNameHandling.Auto, 
                    Converters = new List<JsonConverter>
                        (
                            new JsonConverter[]
                            {
                                new JsonKnownTypeConverter(knownTypes)
                            }
                        )
                }
            );
    }

    public static string SerializeJson(this object objectToSerialize)
    {
        return JsonConvert.SerializeObject(objectToSerialize, Formatting.Indented,
        new JsonSerializerSettings {TypeNameHandling = TypeNameHandling.Auto});
    }
}

Bạn có thể xác định cách so sánh và xác định các loại của riêng mình trong các chuyển đổi, tôi chỉ sử dụng tên lớp.


1
JsonConverter này rất tuyệt, tôi đã sử dụng nó nhưng phải đối mặt với một số vấn đề mà tôi đã giải quyết theo cách này: - Sử dụng JsonSerializer.CreateDefault () thay vì Populate, vì đối tượng của tôi có cấu trúc phân cấp sâu hơn. - Sử dụng phản chiếu để truy xuất hàm tạo và Instanciate nó trong phương thức Create ()
Aurel

3

Thông thường, tôi luôn sử dụng giải pháp TypeNameHandlingtheo đề xuất của DanielT, nhưng trong các trường hợp ở đây, tôi không có quyền kiểm soát JSON đến (và do đó không thể đảm bảo rằng nó bao gồm một thuộc $typetính), tôi đã viết một trình chuyển đổi tùy chỉnh chỉ cho phép bạn chỉ định rõ ràng loại bê tông:

public class Model
{
    [JsonConverter(typeof(ConcreteTypeConverter<Something>))]
    public ISomething TheThing { get; set; }
}

Điều này chỉ sử dụng triển khai trình tuần tự mặc định từ Json.Net trong khi chỉ định rõ ràng loại cụ thể.

Mã nguồn và tổng quan có sẵn trên bài đăng blog này .


1
Đây là một giải pháp tuyệt vời. Chúc mừng.
JohnMetta

2

Tôi chỉ muốn hoàn thành ví dụ mà @Daniel T. đã cho chúng tôi xem ở trên:

Nếu bạn đang sử dụng mã này để tuần tự hóa đối tượng của mình:

var settings = new JsonSerializerSettings();
settings.TypeNameHandling = TypeNameHandling.Objects;
JsonConvert.SerializeObject(entity, Formatting.Indented, settings);

Mã để giải mã json sẽ giống như sau:

var settings = new JsonSerializerSettings(); 
settings.TypeNameHandling = TypeNameHandling.Objects;
var entity = JsonConvert.DeserializeObject<EntityType>(json, settings);

Đây là cách một json được tuân thủ khi sử dụng TypeNameHandlingcờ:nhập mô tả hình ảnh ở đây


-5

Tôi đã tự hỏi điều này tương tự, nhưng tôi sợ rằng nó không thể được thực hiện.

Hãy xem nó theo cách này. Bạn chuyển cho JSon.net một chuỗi dữ liệu và một kiểu để giải mã hóa vào. JSON.net phải làm gì khi nó nhấn ISomething đó? Nó không thể tạo một loại ISomething mới vì ISomething không phải là một đối tượng. Nó cũng không thể tạo một đối tượng triển khai ISomething, vì nó không có manh mối nào trong số nhiều đối tượng có thể kế thừa ISomething mà nó nên sử dụng. Giao diện, là thứ có thể được tự động tuần tự hóa, nhưng không được tự động hóa.

Những gì tôi sẽ làm là xem xét việc thay thế ISomething bằng một lớp cơ sở. Sử dụng nó, bạn có thể có được hiệu ứng mà bạn đang tìm kiếm.


1
Tôi nhận ra rằng nó sẽ không hoạt động "ra khỏi hộp". Nhưng tôi đã tự hỏi liệu có một số thuộc tính như "[JsonProperty (typeof (SomethingBase))]" mà tôi có thể sử dụng để cung cấp một lớp cụ thể hay không.
dthrasher

Vậy tại sao không sử dụng SomethingBase thay vì ISomething trong đoạn mã trên? Có thể lập luận rằng chúng ta cũng đang nhìn nhận điều này một cách sai lầm vì Giao diện không nên được sử dụng trong tuần tự hóa, vì chúng chỉ đơn giản là định nghĩa "giao diện" giao tiếp với một lớp nhất định. Việc tuần tự hóa một giao diện về mặt kỹ thuật là vô nghĩa, cũng như việc tuần tự hóa một lớp trừu tượng. Vì vậy, trong khi nó "có thể được thực hiện", tôi tranh luận rằng nó "không nên được thực hiện".
Timothy Baldridge

Bạn đã xem bất kỳ lớp nào trong Không gian tên Newtonsoft.Json.Serialization chưa? đặc biệt là lớp JsonObjectContract?
johnny

-9

Đây là tham chiếu đến một bài báo được viết bởi ScottGu

Dựa trên đó, tôi đã viết một số mã mà tôi nghĩ có thể hữu ích

public interface IEducationalInstitute
{
    string Name
    {
        get; set;
    }

}

public class School : IEducationalInstitute
{
    private string name;
    #region IEducationalInstitute Members

    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    #endregion
}

public class Student 
{
    public IEducationalInstitute LocalSchool { get; set; }

    public int ID { get; set; }
}

public static class JSONHelper
{
    public static string ToJSON(this object obj)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        return serializer.Serialize(obj);
    }
    public  static string ToJSON(this object obj, int depth)
    {
        JavaScriptSerializer serializer = new JavaScriptSerializer();
        serializer.RecursionLimit = depth;
        return serializer.Serialize(obj);
    }
}

Và đây là cách bạn gọi nó

School myFavSchool = new School() { Name = "JFK High School" };
Student sam = new Student()
{
    ID = 1,
    LocalSchool = myFavSchool
};
string jSONstring = sam.ToJSON();

Console.WriteLine(jSONstring);
//Result {"LocalSchool":{"Name":"JFK High School"},"ID":1}

Nếu tôi hiểu nó một cách chính xác, tôi không nghĩ rằng bạn cần chỉ định một lớp cụ thể triển khai giao diện cho tuần tự hóa JSON.


1
Mẫu của bạn sử dụng JavaScriptSerializer, một lớp trong .NET Framework. Tôi đang sử dụng Json.NET làm bộ tuần tự của mình. codeplex.com/Json
dthrasher 14/02/10

3
Không đề cập đến câu hỏi ban đầu, Json.NET đã được đề cập rõ ràng ở đó.
Oliver
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.