Truyền các giao diện để khử lưu huỳnh trong JSON.NET


128

Tôi đang cố gắng thiết lập một trình đọc sẽ lấy các đối tượng JSON từ các trang web khác nhau (nghĩ là quét thông tin) và dịch chúng thành các đối tượng C #. Tôi hiện đang sử dụng JSON.NET cho quá trình khử lưu huỳnh. Vấn đề tôi gặp phải là nó không biết cách xử lý các thuộc tính cấp giao diện trong một lớp. Vì vậy, một cái gì đó của bản chất:

public IThingy Thing

Sẽ tạo ra lỗi:

Không thể tạo một thể hiện của loại IThingy. Loại là một giao diện hoặc lớp trừu tượng và không thể được khởi tạo.

Điều tương đối quan trọng là phải có một ITakeny trái ngược với Thingy vì mã tôi đang làm việc được coi là nhạy cảm và thử nghiệm đơn vị là rất quan trọng. Không thể nhạo báng các đối tượng cho các kịch bản thử nghiệm nguyên tử với các đối tượng chính thức như Thingy. Chúng phải là một giao diện.

Bây giờ tôi đã xem qua tài liệu của JSON.NET và các câu hỏi tôi có thể tìm thấy trên trang này liên quan đến vấn đề này đều có từ hơn một năm trước. Có ai giúp đỡ không?

Ngoài ra, nếu có vấn đề, ứng dụng của tôi được viết bằng .NET 4.0.


Câu trả lời:


115

@SamualDavis đã cung cấp một giải pháp tuyệt vời trong một câu hỏi liên quan , mà tôi sẽ tóm tắt ở đây.

Nếu bạn phải giải tuần tự hóa một luồng JSON thành một lớp cụ thể có các thuộc tính giao diện, bạn có thể bao gồm các lớp cụ thể làm tham số cho hàm tạo cho lớp! Trình giải nén NewtonSoft đủ thông minh để tìm ra rằng nó cần sử dụng các lớp cụ thể đó để giải tuần tự hóa các thuộc tính.

Đây là một ví dụ:

public class Visit : IVisit
{
    /// <summary>
    /// This constructor is required for the JSON deserializer to be able
    /// to identify concrete classes to use when deserializing the interface properties.
    /// </summary>
    public Visit(MyLocation location, Guest guest)
    {
        Location = location;
        Guest = guest;
    }
    public long VisitId { get; set; }
    public ILocation Location { get;  set; }
    public DateTime VisitDate { get; set; }
    public IGuest Guest { get; set; }
}

15
Làm thế nào điều này sẽ làm việc với một ICollection? ICollection <IGuest> Khách {get; set;}
DrSammyD

12
Nó hoạt động với ICollection <ConcreteClass>, do đó ICollection <Guest> hoạt động. Giống như một FYI, bạn có thể đặt thuộc tính [JsonConstructor] trên hàm tạo của mình để nó sẽ sử dụng nó theo mặc định nếu bạn có nhiều hàm tạo
DrSammyD

6
Tôi bị mắc kẹt với cùng một vấn đề, trong trường hợp của tôi, tôi có một vài triển khai giao diện (trong ví dụ của bạn là giao diện), vậy nếu có các lớp như MyLocation, VIPLocation, OrdinaryLocation. Làm thế nào để ánh xạ những thứ này đến vị trí địa điểm? Nếu bạn chỉ có một triển khai như MyLocation thì thật dễ dàng, nhưng làm thế nào để thực hiện nếu có nhiều triển khai ILocation?
NGÀY 9/10/2015

10
Nếu bạn có nhiều hơn một hàm tạo, bạn có thể đánh dấu hàm tạo đặc biệt của mình bằng [JsonConstructor]thuộc tính.
Bác sĩ Rob Lang

26
Điều này không tốt chút nào. Điểm quan trọng của việc sử dụng các giao diện là sử dụng phép nội xạ phụ thuộc, nhưng thực hiện điều này với một tham số gõ đối tượng được yêu cầu bởi nhà xây dựng của bạn, bạn hoàn toàn hiểu rõ việc có một giao diện là một thuộc tính.
Jérôme MEVEL

57

(Sao chép từ câu hỏi này )

Trong trường hợp tôi không có quyền kiểm soát JSON đến (và vì vậy không thể đảm bảo rằng nó bao gồm thuộc tính $ type) Tôi đã viết một trình chuyển đổi tùy chỉnh chỉ cho phép bạn xác định rõ ràng loại cụ thể:

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

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

Một cái nhìn tổng quan có sẵn trên bài đăng blog này . Mã nguồn dưới đây:

public class ConcreteTypeConverter<TConcrete> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        //assume we can convert to anything for now
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        //explicitly specify the concrete type we want to create
        return serializer.Deserialize<TConcrete>(reader);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        //use the default serialization - it works fine
        serializer.Serialize(writer, value);
    }
}

11
Tôi thực sự thích cách tiếp cận này và áp dụng nó cho dự án của chúng ta. Tôi thậm chí đã thêm một ConcreteListTypeConverter<TInterface, TImplementation>để xử lý các thành viên lớp IList<TInterface>.
Oliver

3
Đó là một chút tuyệt vời của mã. Nó có thể đẹp hơn để có mã thực tế concreteTypeConvertertrong câu hỏi mặc dù.
Chris

2
@Oliver - Bạn có thể đăng bài ConcreteListTypeConverter<TInterface, TImplementation>thực hiện của bạn ?
Michael

2
Và nếu bạn có hai người thực hiện IS Something?
bdaniel7

56

Tại sao nên sử dụng bộ chuyển đổi? Có một chức năng riêng Newtonsoft.Jsonđể giải quyết vấn đề chính xác này:

Đặt TypeNameHandlingtrong JsonSerializerSettingsđểTypeNameHandling.Auto

JsonConvert.SerializeObject(
  toSerialize,
  new JsonSerializerSettings()
  {
    TypeNameHandling = TypeNameHandling.Auto
  });

Điều này sẽ đặt mọi loại vào json, không được coi là một thể hiện cụ thể của một loại mà là một giao diện hoặc một lớp trừu tượng.

Đảm bảo rằng bạn đang sử dụng cùng các cài đặt để tuần tự hóa và giải tuần tự hóa .

Tôi đã thử nó, và nó hoạt động như một lá bùa, thậm chí với các danh sách.

Kết quả tìm kiếm Kết quả web với các liên kết trang web

⚠️ CẢNH BÁO :

Chỉ sử dụng điều này cho json từ một nguồn được biết đến và đáng tin cậy. Người dùng snipsnipsnip đã đề cập chính xác rằng đây thực sự là một vunerability.

Xem CA 2328SCS0028 để biết thêm thông tin.


Nguồn và triển khai thủ công thay thế: Code Inside Blog


3
Hoàn hảo, điều này đã giúp tôi cho một bản sao sâu nhanh chóng và bẩn thỉu ( stackoverflow.com/questions/78536/deep-claming-objects )
Compufreak

1
Các đối tượng @Shimmy: "Bao gồm tên loại .NET khi tuần tự hóa thành cấu trúc đối tượng JSON." Tự động: Bao gồm tên loại .NET khi loại đối tượng được tuần tự hóa không giống với loại khai báo của nó. Lưu ý rằng điều này không bao gồm các đối tượng nối tiếp gốc theo mặc định. Để bao gồm tên loại của đối tượng gốc trong JSON, bạn phải chỉ định một đối tượng loại gốc với serializeObject (Object, Type, JsonSerializerSinstall) hoặc serialize (JsonWriter, Object, Type). "Nguồn: newtonsoft.com/json/help/html/
Mafii

4
Tôi mới thử cái này trên Deserialization và nó không hoạt động. Dòng chủ đề của câu hỏi Stack Overflow này là, "Đúc giao diện để khử lưu huỳnh trong JSON.NET"
Justin Russo

3
@JustinRusso nó chỉ hoạt động khi json đã được nối tiếp với cùng một cài đặt
Mafii

3
Upvote cho giải pháp nhanh chóng, nếu không bẩn. Nếu bạn chỉ là tuần tự hóa cấu hình, điều này hoạt động. Nhịp đập ngừng phát triển để xây dựng bộ chuyển đổi và chắc chắn nhịp đập trang trí cho mọi tài sản được tiêm. serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSinstall (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson

39

Để cho phép khử lưu trữ nhiều triển khai giao diện, bạn có thể sử dụng JsonConverter nhưng không thông qua một thuộc tính:

Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
serializer.Converters.Add(new DTOJsonConverter());
Interfaces.IEntity entity = serializer.Deserialize(jsonReader);

DTOJsonConverter ánh xạ từng giao diện với cách triển khai cụ thể:

class DTOJsonConverter : Newtonsoft.Json.JsonConverter
{
    private static readonly string ISCALAR_FULLNAME = typeof(Interfaces.IScalar).FullName;
    private static readonly string IENTITY_FULLNAME = typeof(Interfaces.IEntity).FullName;


    public override bool CanConvert(Type objectType)
    {
        if (objectType.FullName == ISCALAR_FULLNAME
            || objectType.FullName == IENTITY_FULLNAME)
        {
            return true;
        }
        return false;
    }

    public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer)
    {
        if (objectType.FullName == ISCALAR_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientScalar));
        else if (objectType.FullName == IENTITY_FULLNAME)
            return serializer.Deserialize(reader, typeof(DTO.ClientEntity));

        throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
    }

    public override void WriteJson(Newtonsoft.Json.JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

DTOJsonConverter chỉ được yêu cầu cho trình giải nén. Quá trình tuần tự hóa là không thay đổi. Đối tượng Json không cần nhúng tên loại cụ thể.

Bài đăng SO này cung cấp cùng một giải pháp một bước nữa với JsonConverter chung.


Không phải cuộc gọi của phương thức WriteJson tới serializer.Serialize gây ra tràn ngăn xếp, vì việc gọi tuần tự hóa trên giá trị được tuần tự hóa bởi trình chuyển đổi sẽ khiến phương thức WriteJson của trình chuyển đổi được gọi lại một cách đệ quy?
Triynko

Không nên, nếu phương thức CanConvert () trả về kết quả nhất quán.
Eric Boumendil

3
Tại sao bạn so sánh FullNames khi bạn chỉ có thể so sánh các loại trực tiếp?
Alex Zhukovskiy

Chỉ cần so sánh các loại cũng tốt.
Eric Boumendil

23

Sử dụng lớp này, để ánh xạ kiểu trừu tượng thành kiểu thực:

public class AbstractConverter<TReal, TAbstract> : JsonConverter where TReal : TAbstract
{
    public override Boolean CanConvert(Type objectType) 
        => objectType == typeof(TAbstract);

    public override Object ReadJson(JsonReader reader, Type type, Object value, JsonSerializer jser) 
        => jser.Deserialize<TReal>(reader);

    public override void WriteJson(JsonWriter writer, Object value, JsonSerializer jser) 
        => jser.Serialize(writer, value);
}

... và khi khử lưu huỳnh:

        var settings = new JsonSerializerSettings
        {
            Converters = {
                new AbstractConverter<Thing, IThingy>(),
                new AbstractConverter<Thing2, IThingy2>()
            },
        };

        JsonConvert.DeserializeObject(json, type, settings);

1
Tôi thực sự thích một câu trả lời súc tích tốt đẹp giải quyết vấn đề của tôi. Không cần autofac hay gì cả!
Ben Power

3
Thật đáng để đưa điều này vào khai báo lớp trình chuyển đổi: where TReal : TAbstractđể đảm bảo rằng nó có thể chuyển sang loại
Artemious

1
Một nơi đầy đủ hơn có thể được where TReal : class, TAbstract, new().
Erik Philips

2
Tôi cũng đã sử dụng trình chuyển đổi này với struct, tôi tin rằng "trong đó TReal: TAb bát" là đủ. Cảm ơn tất cả.
Gildor

2
Vàng! Con đường trơn tru để đi.
SwissCoder

12

Nicholas Westby cung cấp một giải pháp tuyệt vời trong một bài viết tuyệt vời .

Nếu bạn muốn Deserializing JSON đến một trong nhiều lớp có thể thực hiện giao diện như thế:

public class Person
{
    public IProfession Profession { get; set; }
}

public interface IProfession
{
    string JobTitle { get; }
}

public class Programming : IProfession
{
    public string JobTitle => "Software Developer";
    public string FavoriteLanguage { get; set; }
}

public class Writing : IProfession
{
    public string JobTitle => "Copywriter";
    public string FavoriteWord { get; set; }
}

public class Samples
{
    public static Person GetProgrammer()
    {
        return new Person()
        {
            Profession = new Programming()
            {
                FavoriteLanguage = "C#"
            }
        };
    }
}

Bạn có thể sử dụng trình chuyển đổi JSON tùy chỉnh:

public class ProfessionConverter : JsonConverter
{
    public override bool CanWrite => false;
    public override bool CanRead => true;
    public override bool CanConvert(Type objectType)
    {
        return objectType == typeof(IProfession);
    }
    public override void WriteJson(JsonWriter writer,
        object value, JsonSerializer serializer)
    {
        throw new InvalidOperationException("Use default serialization.");
    }

    public override object ReadJson(JsonReader reader,
        Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        var jsonObject = JObject.Load(reader);
        var profession = default(IProfession);
        switch (jsonObject["JobTitle"].Value())
        {
            case "Software Developer":
                profession = new Programming();
                break;
            case "Copywriter":
                profession = new Writing();
                break;
        }
        serializer.Populate(jsonObject.CreateReader(), profession);
        return profession;
    }
}

Và bạn sẽ cần trang trí thuộc tính "Profession" bằng thuộc tính JsonConverter để cho nó biết sử dụng trình chuyển đổi tùy chỉnh của bạn:

    public class Person
    {
        [JsonConverter(typeof(ProfessionConverter))]
        public IProfession Profession { get; set; }
    }

Và sau đó, bạn có thể truyền lớp của mình bằng Giao diện:

Person person = JsonConvert.DeserializeObject<Person>(jsonString);

8

Hai điều bạn có thể thử:

Thực hiện mô hình thử / phân tích cú pháp:

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

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

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

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

public class Magnate : IPerson {
  public string Name { get; set; }
  public string IndustryName { get; set; }
}

public class Heir: IPerson {
  public string Name { get; set; }
  public IPerson Benefactor { get; set; }
}

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

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // pseudo-code
    object richDude = serializer.Deserialize<Heir>(reader);

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Magnate>(reader);
    }

    if (richDude == null)
    {
        richDude = serializer.Deserialize<Tycoon>(reader);
    }

    return richDude;
  }

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

Hoặc, nếu bạn có thể làm như vậy trong mô hình đối tượng của mình, hãy triển khai một lớp cơ sở cụ thể giữa IPerson và các đối tượng lá của bạn và giải tuần tự hóa nó.

Cái đầu tiên có khả năng thất bại trong thời gian chạy, cái thứ hai yêu cầu thay đổi mô hình đối tượng của bạn và đồng nhất hóa đầu ra thành mẫu số chung thấp nhất.


Mô hình thử / phân tích không khả thi do quy mô tôi phải làm việc. Tôi phải xem xét một phạm vi của hàng trăm đối tượng cơ sở với hơn hàng trăm đối tượng stub / helper để thể hiện các đối tượng JSON được nhúng xảy ra rất nhiều. Không có gì phải bàn cãi khi thay đổi mô hình đối tượng, nhưng việc không sử dụng lớp cơ sở cụ thể trong các thuộc tính có khiến chúng ta không thể mô phỏng các mục để thử nghiệm đơn vị không? Hay tôi nhận được điều đó lạc hậu bằng cách nào đó?
tmesser

Bạn vẫn có thể triển khai bản giả từ IPerson - lưu ý rằng loại thuộc tính Organisation.Owner vẫn là IPerson. Nhưng để khử lưu lượng của một mục tiêu tùy ý, bạn phải trả về một loại cụ thể. Nếu bạn không sở hữu định nghĩa loại và bạn không thể xác định nhóm thuộc tính tối thiểu mà mã của bạn sẽ yêu cầu, thì phương án cuối cùng của bạn là một cái gì đó giống như túi khóa / giá trị. Sử dụng nhận xét ví dụ trên facebook của bạn - bạn có thể đăng câu trả lời cho việc triển khai ILocation của bạn (một hoặc nhiều) như thế nào không? Điều đó có thể giúp di chuyển mọi thứ về phía trước.
mcw

Vì hy vọng chính là chế nhạo, giao diện ILocation thực sự chỉ là một mặt tiền cho đối tượng cụ thể Location. Một ví dụ nhanh mà tôi vừa làm việc sẽ là một cái gì đó như thế này ( pastebin.com/mWQtqGnB ) cho giao diện và cái này ( pastebin.com/TdJ6cqWV ) cho đối tượng cụ thể.
tmesser

Và để đi bước tiếp theo, đây là một ví dụ về IPage sẽ trông như thế nào ( pastebin.com/iuGifQXp ) và Trang ( pastebin.com/ebqLxzvm ). Tất nhiên, vấn đề là trong khi việc khử lưu huỳnh của Trang nói chung sẽ hoạt động tốt, nó sẽ bị nghẹt thở khi vào thuộc tính ILocation.
tmesser

Ok, vì vậy, suy nghĩ về các đối tượng mà bạn thực sự cạo và khử lưu huỳnh - nói chung có phải là dữ liệu JSON phù hợp với một định nghĩa lớp cụ thể không? Có nghĩa là (theo giả thuyết) bạn sẽ không gặp phải "vị trí" với các thuộc tính bổ sung sẽ khiến Vị trí không phù hợp để sử dụng làm loại cụ thể cho đối tượng khử lưu huỳnh? Nếu vậy, việc gán thuộc tính ILocation của Trang bằng "LocationConverter" sẽ hoạt động. Nếu không, và đó là vì dữ liệu JSON không luôn tuân theo cấu trúc cứng nhắc hoặc nhất quán (như ILocation), sau đó (... tiếp tục)
mcw

8

Tôi thấy điều này hữu ích. Bạn cũng có thể

Cách sử dụng ví dụ

public class Parent
{
    [JsonConverter(typeof(InterfaceConverter<IChildModel, ChildModel>))]
    IChildModel Child { get; set; }
}

Chuyển đổi sáng tạo tùy chỉnh

public class InterfaceConverter<TInterface, TConcrete> : CustomCreationConverter<TInterface>
    where TConcrete : TInterface, new()
{
    public override TInterface Create(Type objectType)
    {
        return new TConcrete();
    }
}

Tài liệu của Json.NET


1
Không phải là một giải pháp khả thi. Không giải quyết Danh sách và dẫn đến rắc trang trí / chú thích ở khắp mọi nơi.
Sean Anderson

5

Đối với những người có thể tò mò về ConcreteListTypeConverter được Oliver tham chiếu, đây là nỗ lực của tôi:

public class ConcreteListTypeConverter<TInterface, TImplementation> : JsonConverter where TImplementation : TInterface 
{
    public override bool CanConvert(Type objectType)
    {
        return true;
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        var res = serializer.Deserialize<List<TImplementation>>(reader);
        return res.ConvertAll(x => (TInterface) x);
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        serializer.Serialize(writer, value);
    }
}

1
Tôi bối rối với sự ghi đè CanConvert(Type objectType) { return true;}. Có vẻ như hacky, chính xác thì điều này hữu ích như thế nào? Tôi có thể sai nhưng không giống như nói với một chiến binh thiếu kinh nghiệm nhỏ hơn rằng họ sẽ chiến thắng trong cuộc chiến bất kể đối thủ?
Chef_Code

4

Đối với những gì nó có giá trị, cuối cùng tôi đã phải tự mình xử lý việc này. Mỗi đối tượng có một phương thức Deserialize (chuỗi jsonStream) . Một vài đoạn của nó:

JObject parsedJson = this.ParseJson(jsonStream);
object thingyObjectJson = (object)parsedJson["thing"];
this.Thing = new Thingy(Convert.ToString(thingyObjectJson));

Trong trường hợp này, Thingy (chuỗi) mới là một hàm tạo sẽ gọi phương thức Deserialize (chuỗi jsonStream) của loại bê tông thích hợp. Lược đồ này sẽ tiếp tục đi xuống và đi xuống cho đến khi bạn đến các điểm cơ bản mà json.NET có thể xử lý.

this.Name = (string)parsedJson["name"];
this.CreatedTime = DateTime.Parse((string)parsedJson["created_time"]);

Vv và Vv. Thiết lập này cho phép tôi cung cấp các thiết lập json.NET mà nó có thể xử lý mà không phải cấu trúc lại một phần lớn của thư viện hoặc sử dụng các mô hình thử / phân tích khó sử dụng có thể làm hỏng toàn bộ thư viện của chúng tôi do số lượng đối tượng liên quan. Điều đó cũng có nghĩa là tôi có thể xử lý hiệu quả mọi thay đổi json trên một đối tượng cụ thể và tôi không cần phải lo lắng về mọi thứ mà đối tượng chạm vào. Đây không phải là giải pháp lý tưởng, nhưng nó hoạt động khá tốt từ thử nghiệm tích hợp và đơn vị của chúng tôi.


4

Giả sử cài đặt tự động như sau:

public class AutofacContractResolver : DefaultContractResolver
{
    private readonly IContainer _container;

    public AutofacContractResolver(IContainer container)
    {
        _container = container;
    }

    protected override JsonObjectContract CreateObjectContract(Type objectType)
    {
        JsonObjectContract contract = base.CreateObjectContract(objectType);

        // use Autofac to create types that have been registered with it
        if (_container.IsRegistered(objectType))
        {
           contract.DefaultCreator = () => _container.Resolve(objectType);
        }  

        return contract;
    }
}

Sau đó, giả sử lớp của bạn là như thế này:

public class TaskController
{
    private readonly ITaskRepository _repository;
    private readonly ILogger _logger;

    public TaskController(ITaskRepository repository, ILogger logger)
    {
        _repository = repository;
        _logger = logger;
    }

    public ITaskRepository Repository
    {
        get { return _repository; }
    }

    public ILogger Logger
    {
        get { return _logger; }
    }
}

Do đó, việc sử dụng trình phân giải trong khử lưu huỳnh có thể như sau:

ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<TaskRepository>().As<ITaskRepository>();
builder.RegisterType<TaskController>();
builder.Register(c => new LogService(new DateTime(2000, 12, 12))).As<ILogger>();

IContainer container = builder.Build();

AutofacContractResolver contractResolver = new AutofacContractResolver(container);

string json = @"{
      'Logger': {
        'Level':'Debug'
      }
}";

// ITaskRespository and ILogger constructor parameters are injected by Autofac 
TaskController controller = JsonConvert.DeserializeObject<TaskController>(json, new JsonSerializerSettings
{
    ContractResolver = contractResolver
});

Console.WriteLine(controller.Repository.GetType().Name);

Bạn có thể xem thêm chi tiết trong http://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htmlm


Tôi sẽ bình chọn đây là giải pháp tốt nhất. DI đã được sử dụng rộng rãi trong những ngày này bởi các nhà phát triển web c # và điều này rất phù hợp như một nơi tập trung để xử lý các chuyển đổi loại đó bởi trình phân giải.
appletwo

3

Không có đối tượng sẽ không bao giờ được một IThingy như các giao diện đều là trừu tượng theo định nghĩa.

Đối tượng bạn có lần đầu tiên được đăng tuần tự là một loại cụ thể , thực hiện giao diện trừu tượng . Bạn cần phải có cùng bê tông lớp hồi sinh dữ liệu nối tiếp.

Các đối tượng kết quả sau đó sẽ có một số loại mà cụ các trừu tượng giao diện bạn đang tìm kiếm.

Từ tài liệu này, bạn có thể sử dụng

(Thingy)JsonConvert.DeserializeObject(jsonString, typeof(Thingy));

khi giải tuần tự hóa để thông báo cho JSON.NET về loại cụ thể.


Đó chính xác là bài viết từ hơn một năm trước mà tôi đã đề cập đến. Gợi ý chính duy nhất (viết bộ chuyển đổi tùy chỉnh) không khả thi lắm với quy mô tôi buộc phải xem xét. JSON.NET đã thay đổi rất nhiều trong năm can thiệp. Tôi hoàn toàn hiểu sự khác biệt giữa một lớp và một giao diện, nhưng C # cũng hỗ trợ chuyển đổi ngầm định từ một giao diện sang một đối tượng thực hiện giao diện liên quan đến việc gõ. Về cơ bản, tôi đang hỏi liệu có cách nào để nói với JSON.NET đối tượng nào sẽ triển khai giao diện này không.
tmesser

Đó là tất cả trong câu trả lời tôi chỉ cho bạn. Hãy chắc chắn rằng có một _typetài sản báo hiệu loại bê tông sẽ sử dụng.
Sean Kinsey

Và tôi hoàn toàn nghi ngờ rằng C # hỗ trợ bất kỳ kiểu đánh máy 'ngầm' nào từ một biến được khai báo là giao diện thành một loại cụ thể mà không có bất kỳ gợi ý nào.
Sean Kinsey

Trừ khi tôi đọc sai, thuộc tính _type được cho là nằm trong JSON được tuần tự hóa. Điều đó hoạt động tốt nếu bạn chỉ giải tuần tự hóa những gì bạn đã tuần tự hóa, nhưng đó không phải là những gì đang diễn ra ở đây. Tôi đang lấy JSON từ một số trang web sẽ không tuân theo tiêu chuẩn đó.
tmesser

@YYY - Bạn có kiểm soát cả tuần tự hóa và giải tuần tự hóa từ JSON nguồn không? Bởi vì cuối cùng bạn sẽ cần phải nhúng loại cụ thể trong JSON được tuần tự hóa như một gợi ý để sử dụng khi giải tuần tự hóa hoặc bạn sẽ cần sử dụng một loại mô hình thử / phân tích phát hiện / cố gắng phát hiện loại cụ thể khi chạy và gọi trình giải nén thích hợp.
mcw

3

Giải pháp của tôi cho vấn đề này, mà tôi thích bởi vì nó rất chung chung, như sau:

/// <summary>
/// Automagically convert known interfaces to (specific) concrete classes on deserialisation
/// </summary>
public class WithMocksJsonConverter : JsonConverter
{
    /// <summary>
    /// The interfaces I know how to instantiate mapped to the classes with which I shall instantiate them, as a Dictionary.
    /// </summary>
    private readonly Dictionary<Type,Type> conversions = new Dictionary<Type,Type>() { 
        { typeof(IOne), typeof(MockOne) },
        { typeof(ITwo), typeof(MockTwo) },
        { typeof(IThree), typeof(MockThree) },
        { typeof(IFour), typeof(MockFour) }
    };

    /// <summary>
    /// Can I convert an object of this type?
    /// </summary>
    /// <param name="objectType">The type under consideration</param>
    /// <returns>True if I can convert the type under consideration, else false.</returns>
    public override bool CanConvert(Type objectType)
    {
        return conversions.Keys.Contains(objectType);
    }

    /// <summary>
    /// Attempt to read an object of the specified type from this reader.
    /// </summary>
    /// <param name="reader">The reader from which I read.</param>
    /// <param name="objectType">The type of object I'm trying to read, anticipated to be one I can convert.</param>
    /// <param name="existingValue">The existing value of the object being read.</param>
    /// <param name="serializer">The serializer invoking this request.</param>
    /// <returns>An object of the type into which I convert the specified objectType.</returns>
    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        try
        {
            return serializer.Deserialize(reader, this.conversions[objectType]);
        }
        catch (Exception)
        {
            throw new NotSupportedException(string.Format("Type {0} unexpected.", objectType));
        }
    }

    /// <summary>
    /// Not yet implemented.
    /// </summary>
    /// <param name="writer">The writer to which I would write.</param>
    /// <param name="value">The value I am attempting to write.</param>
    /// <param name="serializer">the serializer invoking this request.</param>
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        throw new NotImplementedException();
    }
}

}

Rõ ràng, bạn có thể chuyển đổi nó thành một trình chuyển đổi tổng quát hơn bằng cách thêm một hàm tạo lấy một đối số kiểu Dictionary <Type, Type> để khởi tạo biến đối tượng chuyển đổi.


3

Vài năm và tôi đã có một vấn đề tương tự. Trong trường hợp của tôi, có các giao diện lồng nhau rất nhiều và ưu tiên tạo các lớp cụ thể trong thời gian chạy để nó hoạt động với một lớp chung.

Tôi quyết định tạo một lớp proxy trong thời gian chạy kết thúc đối tượng được trả lại bởi Newtonsoft.

Ưu điểm của phương pháp này là nó không yêu cầu triển khai cụ thể lớp và có thể tự động xử lý bất kỳ độ sâu nào của các giao diện lồng nhau. Bạn có thể xem thêm về nó trên blog của tôi .

using Castle.DynamicProxy;
using Newtonsoft.Json.Linq;
using System;
using System.Reflection;

namespace LL.Utilities.Std.Json
{
    public static class JObjectExtension
    {
        private static ProxyGenerator _generator = new ProxyGenerator();

        public static dynamic toProxy(this JObject targetObject, Type interfaceType) 
        {
            return _generator.CreateInterfaceProxyWithoutTarget(interfaceType, new JObjectInterceptor(targetObject));
        }

        public static InterfaceType toProxy<InterfaceType>(this JObject targetObject)
        {

            return toProxy(targetObject, typeof(InterfaceType));
        }
    }

    [Serializable]
    public class JObjectInterceptor : IInterceptor
    {
        private JObject _target;

        public JObjectInterceptor(JObject target)
        {
            _target = target;
        }
        public void Intercept(IInvocation invocation)
        {

            var methodName = invocation.Method.Name;
            if(invocation.Method.IsSpecialName && methodName.StartsWith("get_"))
            {
                var returnType = invocation.Method.ReturnType;
                methodName = methodName.Substring(4);

                if (_target == null || _target[methodName] == null)
                {
                    if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                    {

                        invocation.ReturnValue = null;
                        return;
                    }

                }

                if (returnType.GetTypeInfo().IsPrimitive || returnType.Equals(typeof(string)))
                {
                    invocation.ReturnValue = _target[methodName].ToObject(returnType);
                }
                else
                {
                    invocation.ReturnValue = ((JObject)_target[methodName]).toProxy(returnType);
                }
            }
            else
            {
                throw new NotImplementedException("Only get accessors are implemented in proxy");
            }

        }
    }



}

Sử dụng:

var jObj = JObject.Parse(input);
InterfaceType proxyObject = jObj.toProxy<InterfaceType>();

Cảm ơn! Đây là câu trả lời duy nhất hỗ trợ đúng cách gõ động (gõ vịt) mà không buộc các hạn chế đối với json đến.
Philip Pittle

Không vấn đề gì. Tôi hơi ngạc nhiên khi thấy không có gì ngoài đó. Nó đã di chuyển một chút kể từ ví dụ ban đầu đó nên tôi quyết định chia sẻ mã. github.com/sudsy/JsonDuckTyper . Tôi cũng đã xuất bản nó trên nuget với tên JsonDuckTyper. Nếu bạn thấy bạn muốn nâng cao nó, chỉ cần gửi cho tôi một PR và tôi sẽ vui lòng bắt buộc.
Sudsy

Khi tôi đang tìm kiếm một giải pháp trong lĩnh vực này, tôi đã bắt gặp github.com/ekonbenefits/impromptu-interface . Nó không hoạt động trong trường hợp của tôi vì nó không hỗ trợ dotnet core 1.0 nhưng nó có thể hoạt động cho bạn.
Sudsy

Tôi đã thử với Giao diện Impromptu, nhưng Json.Net không vui khi thực hiện PopulateObjecttrên proxy được tạo bởi Giao diện Impromptu. Tôi không may từ bỏ việc nhập Duck Typing - thật dễ dàng hơn để tạo một Trình tạo hợp đồng Json tùy chỉnh sử dụng sự phản chiếu để tìm một triển khai hiện có của giao diện được yêu cầu và sử dụng giao diện đó.
Philip Pittle

1

Sử dụng JsonKnownTypes này , cách sử dụng rất giống nhau, nó chỉ cần thêm phân biệt đối xử vào json:

[JsonConverter(typeof(JsonKnownTypeConverter<Interface1>))]
[JsonKnownType(typeof(MyClass), "myClass")]
public interface Interface1
{  }
public class MyClass : Interface1
{
    public string Something;
}

Bây giờ khi bạn tuần tự hóa đối tượng trong json sẽ được thêm "$type"bằng"myClass" giá trị và nó sẽ được sử dụng cho deserialize

Json:

{"Something":"something", "$type":"derived"}

0

Giải pháp của tôi đã được thêm các yếu tố giao diện trong hàm tạo.

public class Customer: ICustomer{
     public Customer(Details details){
          Details = details;
     }

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
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.