การหล่ออินเทอร์เฟซสำหรับ deserialization ใน JSON.NET


129

ฉันกำลังพยายามตั้งค่าเครื่องอ่านที่จะรับวัตถุ JSON จากเว็บไซต์ต่างๆ (คิดว่าการขูดข้อมูล) และแปลเป็นวัตถุ C # ฉันกำลังใช้ JSON.NET สำหรับกระบวนการ deserialization ปัญหาที่ฉันพบคือไม่ทราบวิธีจัดการคุณสมบัติระดับอินเตอร์เฟสในคลาส ดังนั้นบางอย่างของธรรมชาติ:

public IThingy Thing

จะทำให้เกิดข้อผิดพลาด:

ไม่สามารถสร้างอินสแตนซ์ประเภท IThingy Type เป็นอินเทอร์เฟซหรือคลาสนามธรรมและไม่สามารถสร้างอินสแตนซ์ได้

มันค่อนข้างสำคัญที่จะต้องเป็น IThingy เมื่อเทียบกับ Thingy เนื่องจากโค้ดที่ฉันกำลังทำงานอยู่นั้นถือว่ามีความละเอียดอ่อนและการทดสอบหน่วยเป็นสิ่งที่สำคัญมาก การจำลองวัตถุสำหรับสคริปต์ทดสอบอะตอมไม่สามารถทำได้กับวัตถุที่มีคุณสมบัติครบถ้วนเช่น Thingy ต้องเป็นอินเทอร์เฟซ

ฉันอ่านเอกสารของ JSON.NET มาระยะหนึ่งแล้วและคำถามที่ฉันพบในไซต์นี้ที่เกี่ยวข้องกับเรื่องนี้ล้วนมาจากปีที่แล้ว ความช่วยเหลือใด ๆ

นอกจากนี้หากเป็นเรื่องสำคัญแอปของฉันเขียนด้วย. NET 4.0


คำตอบ:


115

@SamualDavis ให้คำตอบที่ดีในคำถามที่เกี่ยวข้องซึ่งฉันจะสรุปที่นี่

หากคุณต้อง deserialize สตรีม JSON ลงในคลาสคอนกรีตที่มีคุณสมบัติอินเทอร์เฟซคุณสามารถรวมคลาสคอนกรีตเป็นพารามิเตอร์ให้กับคอนสตรัคเตอร์สำหรับคลาสได้! NewtonSoft deserializer ฉลาดพอที่จะคิดออกว่าจำเป็นต้องใช้คลาสคอนกรีตเหล่านั้นเพื่อแยกค่าคุณสมบัติ

นี่คือตัวอย่าง:

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
วิธีนี้ใช้กับ ICollection ได้อย่างไร? ICollection <IGuest> แขกรับเชิญ {get; set;}
DrSammyD

12
ทำงานร่วมกับ ICollection <ConcreteClass> ดังนั้น ICollection <Guest> จึงทำงานได้ เช่นเดียวกับ FYI คุณสามารถใส่แอตทริบิวต์ [JsonConstructor] บนตัวสร้างของคุณเพื่อที่จะใช้สิ่งนั้นตามค่าเริ่มต้นหากคุณมีตัวสร้างหลายตัว
DrSammyD

6
ฉันติดปัญหาเดียวกันในกรณีของฉันฉันมีการใช้งานอินเทอร์เฟซหลายตัว (ในตัวอย่างของคุณอินเทอร์เฟซคือ ILocation) ดังนั้นจะเกิดอะไรขึ้นถ้ามีคลาสเช่น MyLocation, VIPLocation, OrdinaryLocation จะแมปสิ่งเหล่านี้กับคุณสมบัติ Location ได้อย่างไร? หากคุณมีการใช้งานเพียงครั้งเดียวเช่น MyLocation มันง่าย แต่จะทำอย่างไรหากมีการใช้งาน ILocation หลายรายการ?
อาเธอร์

10
หากคุณมีตัวสร้างมากกว่าหนึ่งตัวคุณสามารถมาร์กอัปตัวสร้างพิเศษของคุณด้วย[JsonConstructor]แอ็ตทริบิวต์
Dr Rob Lang

26
นี้ไม่ดีเลย จุดประสงค์ของการใช้อินเทอร์เฟซคือการใช้การฉีดขึ้นต่อกัน แต่การทำเช่นนี้กับพารามิเตอร์ที่พิมพ์อ็อบเจ็กต์ที่ตัวสร้างของคุณต้องการคุณจะทำให้จุดที่มีอินเทอร์เฟซเป็นคุณสมบัติ
Jérôme MEVEL

57

(คัดลอกมาจากคำถามนี้ )

ในกรณีที่ฉันไม่สามารถควบคุม JSON ที่เข้ามาได้ (ดังนั้นจึงไม่สามารถมั่นใจได้ว่ามีคุณสมบัติ $ type) ฉันได้เขียนตัวแปลงที่กำหนดเองซึ่งช่วยให้คุณระบุประเภทคอนกรีตได้อย่างชัดเจน:

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

เพียงแค่ใช้การใช้งาน serializer เริ่มต้นจาก Json.Net ในขณะที่ระบุประเภทคอนกรีตอย่างชัดเจน

ภาพรวมมีอยู่ในโพสต์บล็อกนี้ ซอร์สโค้ดอยู่ด้านล่าง:

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
ฉันชอบแนวทางนี้มากและนำไปใช้กับโครงการของเราเอง ฉันยังเพิ่มให้กับสมาชิกในการจัดการชั้นเรียนของประเภทConcreteListTypeConverter<TInterface, TImplementation> IList<TInterface>
Oliver

3
นั่นเป็นรหัสที่ยอดเยี่ยม อาจจะดีกว่าหากมีรหัสจริงสำหรับconcreteTypeConverterคำถาม
คริส

2
@Oliver - คุณสามารถโพสต์ConcreteListTypeConverter<TInterface, TImplementation>การใช้งานของคุณได้หรือไม่?
Michael

2
และถ้าคุณมีผู้ใช้ ISomething สองคนล่ะ?
bdaniel7

56

ทำไมต้องใช้ตัวแปลง? มีฟังก์ชันดั้งเดิมในNewtonsoft.Jsonการแก้ปัญหานี้:

การตั้งค่าTypeNameHandlingในJsonSerializerSettingsการTypeNameHandling.Auto

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

สิ่งนี้จะใส่ทุกประเภทลงใน json ซึ่งไม่ได้ถือเป็นตัวอย่างที่เป็นรูปธรรมของประเภท แต่เป็นอินเทอร์เฟซหรือคลาสนามธรรม

ตรวจสอบให้แน่ใจว่าคุณใช้การตั้งค่าเดียวกันสำหรับการทำให้เป็นอนุกรมและการดีซีเรียลไลเซชัน

ฉันทดสอบแล้วและมันใช้งานได้อย่างมีเสน่ห์แม้กระทั่งกับรายการ

Search Results ผลการค้นหาเว็บที่มีลิงค์ของเว็บไซต์

⚠️ คำเตือน :

ใช้สิ่งนี้สำหรับ json จากแหล่งที่รู้จักและเชื่อถือได้เท่านั้น ผู้ใช้snipsnipsnipกล่าวอย่างถูกต้องว่านี่เป็นความสามารถในการทำงาน

ดูCA2328และSCS0028สำหรับข้อมูลเพิ่มเติม


แหล่งที่มาและการใช้งานด้วยตนเองทางเลือก: Code Inside Blog


3
สมบูรณ์แบบสิ่งนี้ช่วยฉันในการโคลนลึกที่รวดเร็วและสกปรก ( stackoverflow.com/questions/78536/deep-cloning-objects )
Compufreak

1
@Shimmy Objects: "รวมชื่อประเภท. NET เมื่อทำให้เป็นอนุกรมในโครงสร้างออบเจ็กต์ JSON" อัตโนมัติ: รวมชื่อประเภท. NET เมื่อประเภทของวัตถุที่ถูกทำให้เป็นอนุกรมไม่เหมือนกับประเภทที่ประกาศไว้ โปรดทราบว่าสิ่งนี้ไม่รวมออบเจ็กต์ที่ทำให้เป็นอนุกรมของรูทโดยค่าเริ่มต้น ในการรวมชื่อชนิดของออบเจ็กต์รูทใน JSON คุณต้องระบุอ็อบเจ็กต์ประเภทรูทด้วย SerializeObject (Object, Type, JsonSerializerSettings) หรือ Serialize (JsonWriter, Object, Type) "ที่มา: newtonsoft.com/json/help/html/…
Mafii

4
ฉันเพิ่งลองสิ่งนี้กับ Deserialization และไม่ได้ผล บรรทัดหัวเรื่องของคำถาม Stack Overflow นี้คือ "การหล่ออินเทอร์เฟซสำหรับ deserialization ใน JSON.NET"
Justin Russo

3
@JustinRusso มันจะใช้งานได้ก็ต่อเมื่อ json ถูกทำให้เป็นอนุกรมด้วยการตั้งค่าเดียวกัน
Mafii

3
โหวตให้วิธีแก้ปัญหาอย่างรวดเร็วหากไม่สกปรก หากคุณเป็นเพียงการกำหนดค่าการกำหนดค่าแบบอนุกรมก็ใช้ได้ Beats หยุดการพัฒนาเพื่อสร้างคอนเวอร์เตอร์และเอาชนะการตกแต่งทุกคุณสมบัติที่ฉีดเข้าไป serializer.TypeNameHandling = TypeNameHandling.Auto; JsonConvert.DefaultSettings (). TypeNameHandling = TypeNameHandling.Auto;
Sean Anderson

39

ในการเปิดใช้งาน deserialization ของการใช้งานอินเทอร์เฟซหลาย ๆ ตัวคุณสามารถใช้ JsonConverter แต่ไม่ต้องใช้แอตทริบิวต์:

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

DTOJsonConverter แมปแต่ละอินเทอร์เฟซด้วยการนำไปใช้อย่างเป็นรูปธรรม:

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 จำเป็นสำหรับ deserializer เท่านั้น กระบวนการทำให้เป็นอนุกรมไม่มีการเปลี่ยนแปลง วัตถุ Json ไม่จำเป็นต้องฝังชื่อชนิดคอนกรีต

โพสต์ SOนี้นำเสนอโซลูชันเดียวกันอีกขั้นหนึ่งด้วย JsonConverter ทั่วไป


จะไม่เรียกวิธีการ WriteJson ไปที่ serializer การทำซีเรียลไลซ์ทำให้เกิดสแต็กล้นเนื่องจากการเรียกซีเรียลตามค่าที่ถูกทำให้เป็นอนุกรมโดยตัวแปลงจะทำให้เมธอด WriteJson ของตัวแปลงถูกเรียกซ้ำอีกครั้งหรือไม่?
Triynko

ไม่ควรหากเมธอด CanConvert () ส่งคืนผลลัพธ์ที่สอดคล้องกัน
Eric Boumendil

3
ทำไมคุณถึงเปรียบเทียบFullNames ในเมื่อคุณอาจเปรียบเทียบประเภทโดยตรง?
Alex Zhukovskiy

แค่เปรียบเทียบประเภทก็ยังใช้ได้
Eric Boumendil

23

ใช้คลาสนี้เพื่อจับคู่ประเภทนามธรรมกับประเภทจริง:

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);
}

... และเมื่อ deserialize:

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

        JsonConvert.DeserializeObject(json, type, settings);

1
ฉันชอบคำตอบที่กระชับและช่วยแก้ปัญหาของฉันได้ ไม่จำเป็นต้องใช้ autofac หรืออะไรเลย!
Ben Power

3
มันคุ้มค่าที่จะนำสิ่งนี้ไปใช้ในการประกาศคลาสตัวแปลง: where TReal : TAbstractเพื่อให้แน่ใจว่าสามารถส่งเป็นประเภท
Artemious

1
where TReal : class, TAbstract, new()สมบูรณ์มากขึ้นที่อาจจะ
Erik Philips

2
ฉันใช้ตัวแปลงนี้กับโครงสร้างด้วยเช่นกันฉันเชื่อว่า "ที่ TReal: TAbstract" ก็เพียงพอแล้ว ขอบคุณทุกคน
Gildor

2
ทอง! วิธีที่เนียนไป
SwissCoder

12

Nicholas Westby เป็นทางออกที่ยอดเยี่ยมในบทความที่ยอดเยี่ยม

หากคุณต้องการ Deserializing JSON เป็นหนึ่งในหลาย ๆ คลาสที่เป็นไปได้ที่ใช้อินเทอร์เฟซเช่นนั้น:

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#"
            }
        };
    }
}

คุณสามารถใช้ตัวแปลง JSON แบบกำหนดเอง:

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;
    }
}

และคุณจะต้องตกแต่งคุณสมบัติ "Profession" ด้วยแอตทริบิวต์ JsonConverter เพื่อแจ้งให้ใช้ตัวแปลงที่กำหนดเองของคุณ:

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

จากนั้นคุณสามารถแคสต์คลาสของคุณด้วยอินเทอร์เฟซ:

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

8

สองสิ่งที่คุณอาจลอง:

ใช้แบบจำลอง try / parse:

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();
  }
}

หรือถ้าคุณสามารถทำได้ในแบบจำลองวัตถุของคุณให้ใช้คลาสพื้นฐานที่เป็นรูปธรรมระหว่าง IPerson และวัตถุใบไม้ของคุณ

ครั้งแรกอาจล้มเหลวในรันไทม์ตัวที่สองต้องการการเปลี่ยนแปลงโมเดลอ็อบเจ็กต์ของคุณและทำให้เอาต์พุตเป็นเนื้อเดียวกันเป็นตัวหารร่วมที่ต่ำที่สุด


แบบจำลอง try / parse ไม่สามารถทำได้เนื่องจากสเกลที่ฉันต้องใช้ ฉันต้องพิจารณาขอบเขตของออบเจ็กต์พื้นฐานหลายร้อยรายการที่มีวัตถุต้นขั้ว / ตัวช่วยอีกหลายร้อยรายการเพื่อแสดงวัตถุ JSON แบบฝังที่เกิดขึ้นมากมาย การเปลี่ยนโมเดลอ็อบเจ็กต์ไม่ใช่หมดคำถาม แต่การไม่ใช้คลาสฐานคอนกรีตในคุณสมบัติทำให้เราไม่สามารถเลียนแบบรายการสำหรับการทดสอบหน่วยได้หรือไม่? หรือฉันกำลังถอยหลังอย่างนั้น?
tmesser

คุณยังสามารถใช้การจำลองจาก IPerson ได้ - โปรดทราบว่าประเภทของ Organisation คุณสมบัติของเจ้าของยังคงเป็น IPerson แต่สำหรับการกำหนดเป้าหมายโดยพลการคุณต้องส่งคืนประเภทคอนกรีต หากคุณไม่ได้เป็นเจ้าของคำจำกัดความประเภทและคุณไม่สามารถกำหนดชุดคุณสมบัติขั้นต่ำที่รหัสของคุณต้องการได้ทางเลือกสุดท้ายของคุณก็คือกระเป๋ากุญแจ / ค่า การใช้ความคิดเห็นตัวอย่าง Facebook ของคุณ - คุณสามารถโพสต์คำตอบได้ว่าการใช้งาน ILocation ของคุณ (หนึ่งหรือหลายรายการ) มีลักษณะอย่างไร นั่นอาจช่วยขับเคลื่อนสิ่งต่างๆไปข้างหน้า
mcw

เนื่องจากความหวังเบื้องต้นคือการเยาะเย้ยอินเทอร์เฟซของ ILocation จึงเป็นเพียงส่วนหน้าของวัตถุคอนกรีต Location เท่านั้น ตัวอย่างสั้น ๆ ที่ฉันเพิ่งทำขึ้นมาจะเป็นแบบนี้ ( pastebin.com/mWQtqGnB ) สำหรับอินเทอร์เฟซและสิ่งนี้ ( pastebin.com/TdJ6cqWV ) สำหรับวัตถุคอนกรีต
tmesser

และในขั้นตอนต่อไปนี่คือตัวอย่างลักษณะของ IPage ( pastebin.com/iuGifQXp ) และ Page ( pastebin.com/ebqLxzvm ) แน่นอนว่าปัญหาก็คือแม้ว่าโดยทั่วไปแล้วการดีซีเรียลไลเซชั่นของเพจจะทำงานได้ดี แต่ก็จะสำลักเมื่อไปถึงคุณสมบัติ ILocation
tmesser

ตกลงดังนั้นการคิดถึงวัตถุที่คุณกำลังขูดและ deserializing โดยทั่วไปแล้วข้อมูล JSON สอดคล้องกับนิยามคลาสคอนกรีตเดียวหรือไม่? ความหมาย (สมมุติฐาน) คุณจะไม่พบ "สถานที่" ที่มีคุณสมบัติเพิ่มเติมที่จะทำให้ Location ไม่เหมาะสมที่จะใช้เป็นประเภทคอนกรีตสำหรับวัตถุ deserialized? หากเป็นเช่นนั้นการระบุคุณสมบัติ ILocation ของเพจด้วย "LocationConverter" ควรใช้งานได้ หากไม่เป็นเช่นนั้นและเป็นเพราะข้อมูล JSON ไม่สอดคล้องกับโครงสร้างที่เข้มงวดหรือสอดคล้องกันเสมอไป (เช่น ILocation) ดังนั้น (... ต่อ)
mcw

8

ฉันพบว่าสิ่งนี้มีประโยชน์ คุณก็เช่นกัน

ตัวอย่างการใช้งาน

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

ตัวแปลงการสร้างแบบกำหนดเอง

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

เอกสาร Json.NET


1
ไม่ใช่วิธีแก้ปัญหาที่ใช้การได้ ไม่ได้กล่าวถึงรายการและนำไปสู่การประดับตกแต่ง / คำอธิบายประกอบทุกที่
Sean Anderson

5

สำหรับผู้ที่อาจสงสัยเกี่ยวกับ ConcreteListTypeConverter ที่ Oliver อ้างถึงนี่คือความพยายามของฉัน:

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
CanConvert(Type objectType) { return true;}ฉันสับสนกับแทนที่ ดูเหมือนว่าแฮ็กข้อมูลนี้มีประโยชน์อย่างไร? ฉันอาจจะคิดผิด แต่ก็ไม่เหมือนกับการบอกนักสู้รุ่นเล็กที่ไม่มีประสบการณ์ว่าพวกเขาจะชนะการต่อสู้ไม่ว่าคู่ต่อสู้จะเป็นอย่างไร
Chef_Code

4

สำหรับสิ่งที่คุ้มค่าฉันต้องจัดการเรื่องนี้ด้วยตัวเองเป็นส่วนใหญ่ วัตถุแต่ละคนมีdeserialize (สตริง jsonStream)วิธีการ ตัวอย่างบางส่วนของมัน:

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

ในกรณีนี้Thingy (สตริง) ใหม่คือตัวสร้างที่จะเรียกเมธอด Deserialize (string jsonStream)ของประเภทคอนกรีตที่เหมาะสม โครงร่างนี้จะลดลงเรื่อย ๆ จนกว่าคุณจะไปถึงจุดฐานที่ json.NET สามารถจัดการได้

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

ไปเรื่อย ๆ การตั้งค่านี้ทำให้ฉันสามารถให้การตั้งค่า json.NET ที่สามารถจัดการได้โดยไม่ต้อง refactor ส่วนใหญ่ของไลบรารีเองหรือใช้แบบจำลองการลอง / แยกวิเคราะห์ที่ไม่สะดวกซึ่งจะทำให้ห้องสมุดทั้งหมดของเราจมลงเนื่องจากมีวัตถุจำนวนมากที่เกี่ยวข้อง นอกจากนี้ยังหมายความว่าฉันสามารถจัดการกับการเปลี่ยนแปลง json บนวัตถุเฉพาะได้อย่างมีประสิทธิภาพและฉันไม่จำเป็นต้องกังวลกับทุกสิ่งที่วัตถุสัมผัส มันไม่ได้เป็นทางออกที่ดี แต่ทำงานได้ดีจากการทดสอบหน่วยและการรวมของเรา


4

สมมติว่าการตั้งค่า autofac ดังต่อไปนี้:

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;
    }
}

จากนั้นสมมติว่าชั้นเรียนของคุณเป็นดังนี้:

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; }
    }
}

ดังนั้นการใช้ตัวแก้ไขในการแยกสารอาจเป็นดังนี้:

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);

สามารถดูรายละเอียดเพิ่มเติมได้ในhttp://www.newtonsoft.com/json/help/html/DeserializeWithDependencyInjection.htm


ฉันจะโหวตให้เป็นทางออกที่ดีที่สุด DI ได้รับการใช้อย่างแพร่หลายในปัจจุบันโดย c # web devs และสิ่งนี้เหมาะอย่างยิ่งในฐานะที่เป็นศูนย์กลางในการจัดการการแปลงประเภทเหล่านั้นโดยตัวแก้ไข
appletwo

3

ไม่มีวัตถุใดที่ จะเป็น IThingy ได้เนื่องจากอินเทอร์เฟซเป็นนามธรรมตามคำจำกัดความ

ออบเจ็กต์ที่คุณมีซึ่งถูกทำให้เป็นอนุกรมเป็นครั้งแรกเป็นประเภทคอนกรีตโดยใช้อินเทอร์เฟซนามธรรม คุณต้องมีคลาสคอนกรีตเดียวกันนี้ในการฟื้นฟูข้อมูลที่เป็นอนุกรม

วัตถุที่เกิดแล้วจะมีบางชนิดที่นำไปปฏิบัตินามธรรมอินเตอร์เฟซที่คุณกำลังมองหา

จากเอกสารดังต่อไปนี้คุณสามารถใช้ได้

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

เมื่อ deserializing เพื่อแจ้ง JSON.NET เกี่ยวกับประเภทคอนกรีต


นั่นคือโพสต์จากปีที่แล้วที่ฉันอ้างถึง ข้อเสนอแนะที่สำคัญเพียงอย่างเดียว (การเขียนตัวแปลงที่กำหนดเอง) ไม่สามารถทำได้อย่างมากกับมาตราส่วนที่ฉันถูกบังคับให้พิจารณา JSON.NET มีการเปลี่ยนแปลงมากในปีที่มีการแทรกแซง ฉันเข้าใจดีถึงความแตกต่างระหว่างคลาสและอินเทอร์เฟซ แต่ C # ยังรองรับการแปลงโดยนัยจากอินเทอร์เฟซไปยังอ็อบเจ็กต์ที่ใช้อินเทอร์เฟซเกี่ยวกับการพิมพ์ ฉันถามเป็นหลักว่ามีวิธีบอก JSON.NET ว่าวัตถุใดจะใช้อินเทอร์เฟซนี้
tmesser

ทั้งหมดอยู่ที่นั่นในคำตอบที่ฉันชี้ให้คุณ ตรวจสอบให้แน่ใจว่ามี_typeคุณสมบัติที่ส่งสัญญาณประเภทคอนกรีตที่จะใช้
Sean Kinsey

และฉันสงสัยอย่างยิ่งว่า C # สนับสนุนการพิมพ์ 'โดยนัย' ทุกประเภทจากตัวแปรที่ประกาศเป็นส่วนต่อประสานกับประเภทคอนกรีตโดยไม่มีคำใบ้ใด ๆ
Sean Kinsey

ถ้าฉันอ่านไม่ผิดคุณสมบัติ _type ควรจะอยู่ใน JSON เพื่อให้เป็นอนุกรม วิธีนี้ใช้ได้ดีถ้าคุณเพียงแค่แยกส่วนของสิ่งที่คุณทำให้เป็นซีเรียลแล้ว แต่นั่นไม่ใช่สิ่งที่เกิดขึ้นที่นี่ ฉันกำลังดึง JSON จากไซต์จำนวนมากที่จะไม่เป็นไปตามมาตรฐานนั้น
tmesser

@YYY - คุณควบคุมทั้ง serialization และ deserialization จาก JSON ต้นทางหรือไม่ เพราะท้ายที่สุดแล้วคุณจะต้องฝังชนิดคอนกรีตใน JSON ที่เป็นอนุกรมเพื่อเป็นคำใบ้เพื่อใช้ในการ deserializing หรือคุณจะต้องใช้แบบจำลอง try / parse บางประเภทที่ตรวจจับ / พยายามตรวจจับชนิดคอนกรีตขณะรันไทม์ และเรียกใช้ deserializer ที่เหมาะสม
mcw

3

วิธีแก้ปัญหาของฉันสำหรับสิ่งนี้ซึ่งฉันชอบเพราะเป็นเรื่องทั่วไปมีดังนี้:

/// <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();
    }
}

}

คุณสามารถแปลงเป็นตัวแปลงทั่วไปได้อย่างเห็นได้ชัดและเล็กน้อยโดยการเพิ่มตัวสร้างซึ่งใช้อาร์กิวเมนต์ประเภท Dictionary <Type, Type> ซึ่งใช้ในการสร้างอินสแตนซ์ของตัวแปรอินสแตนซ์การแปลง


3

หลายปีที่ผ่านมาและฉันมีปัญหาที่คล้ายกัน ในกรณีของฉันมีอินเทอร์เฟซที่ซ้อนกันมากและการตั้งค่าสำหรับการสร้างคลาสคอนกรีตที่รันไทม์เพื่อให้สามารถทำงานกับคลาสทั่วไปได้

ฉันตัดสินใจที่จะสร้างคลาสพร็อกซีในขณะทำงานที่ห่อหุ้มอ็อบเจ็กต์ที่ส่งคืนโดย Newtonsoft

ข้อดีของแนวทางนี้คือไม่ต้องการการนำคลาสไปใช้อย่างเป็นรูปธรรมและสามารถจัดการกับอินเทอร์เฟซที่ซ้อนกันได้โดยอัตโนมัติ ท่านสามารถดูรายละเอียดเพิ่มเติมเกี่ยวกับเรื่องของฉันบล็อก

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");
            }

        }
    }



}

การใช้งาน:

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

ขอบคุณ! นี่เป็นคำตอบเดียวที่รองรับการพิมพ์แบบไดนามิกอย่างถูกต้อง (การพิมพ์แบบเป็ด) โดยไม่บังคับให้มีข้อ จำกัด กับ json ที่เข้ามา
Philip Pittle

ไม่มีปัญหา. ฉันประหลาดใจเล็กน้อยที่เห็นว่าไม่มีอะไรอยู่ในนั้น มันมีความเคลื่อนไหวเล็กน้อยตั้งแต่ตัวอย่างดั้งเดิมดังนั้นฉันจึงตัดสินใจแชร์รหัส github.com/sudsy/JsonDuckTyper ฉันยังเผยแพร่บน nuget ในชื่อ JsonDuckTyper หากคุณพบว่าคุณต้องการเพิ่มประสิทธิภาพเพียงแค่ส่ง PR มาให้ฉันเรายินดีที่จะรับผิดชอบ
Sudsy

เมื่อฉันกำลังมองหาวิธีแก้ปัญหาในพื้นที่นี้ฉันก็เจอgithub.com/ekonbenefits/impromptu-interfaceด้วย มันใช้ไม่ได้ในกรณีของฉันเนื่องจากไม่รองรับ dotnet core 1.0 แต่อาจใช้งานได้สำหรับคุณ
Sudsy

ฉันลองใช้ Impromptu Interface แล้ว แต่ Json.Net ไม่มีความสุขในการทำPopulateObjectบนพร็อกซีที่สร้างโดย Impromptu Interface โชคไม่ดีที่ฉันยอมแพ้ Duck Typing - มันง่ายกว่าที่จะสร้าง Json Contract Serializer ที่กำหนดเองซึ่งใช้การสะท้อนเพื่อค้นหาการใช้งานอินเทอร์เฟซที่ร้องขอที่มีอยู่และใช้สิ่งนั้น
Philip Pittle

1

ใช้JsonKnownTypesนี้ซึ่งเป็นวิธีการใช้ที่คล้ายกันมากเพียงแค่เพิ่มตัวเลือกให้กับ json:

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

ตอนนี้เมื่อคุณทำให้เป็นซีเรียลไลซ์อ็อบเจ็กต์ใน json จะถูกเพิ่ม"$type"ด้วย"myClass"ค่าและจะใช้สำหรับ deserialize

json:

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

0

โซลูชันของฉันถูกเพิ่มองค์ประกอบอินเทอร์เฟซในตัวสร้าง

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

     [JsonProperty("Details",NullValueHnadling = NullValueHandling.Ignore)]
     public IDetails Details {get; set;}
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.