การใช้ตัวแปลง Json.NET เพื่อ deserialize คุณสมบัติ


88

ฉันมีนิยามคลาสที่มีคุณสมบัติที่ส่งคืนอินเทอร์เฟซ

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

    public ISomething Thing { get; set; }
}

การพยายามทำให้คลาส Foo เป็นซีเรียลโดยใช้ Json.NET ทำให้ฉันมีข้อความแสดงข้อผิดพลาด "ไม่สามารถสร้างอินสแตนซ์ประเภท 'ISomething' ได้ไอบางอย่างอาจเป็นอินเทอร์เฟซหรือคลาสนามธรรม"

มีแอตทริบิวต์ Json.NET หรือตัวแปลงที่จะให้ฉันระบุSomethingคลาสคอนกรีตที่จะใช้ระหว่างการดีซีเรียลไลเซชันหรือไม่


ฉันเชื่อว่าคุณต้องระบุชื่อคุณสมบัติที่ได้รับ / ตั้งค่า ISomething
ram

ฉันมี. ฉันใช้ชวเลขสำหรับคุณสมบัติที่ใช้งานอัตโนมัติที่แนะนำใน C # 3.5 msdn.microsoft.com/en-us/library/bb384054.aspx
dthrasher

4
ไม่ใช่สิ่งที่เป็นประเภท ฉันคิดว่ารามถูกต้องคุณยังต้องมีชื่อคุณสมบัติ ฉันรู้ว่าสิ่งนี้ไม่เกี่ยวข้องกับปัญหาของคุณ แต่ความคิดเห็นของคุณด้านบนทำให้ฉันคิดว่าฉันขาดคุณสมบัติใหม่บางอย่างใน. NET ที่อนุญาตให้คุณระบุคุณสมบัติที่ไม่มีชื่อ
Mr Moose

คำตอบ:


92

สิ่งหนึ่งที่คุณสามารถทำได้กับJson.NETคือ:

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

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

TypeNameHandlingธงจะเพิ่ม$typeคุณสมบัติให้กับ JSON ซึ่งจะช่วยให้ Json.NET ที่จะทราบว่าประเภทคอนกรีตจะต้อง deserialize วัตถุลง สิ่งนี้ช่วยให้คุณสามารถยกเลิกการกำหนดค่าวัตถุในขณะที่ยังคงใช้อินเตอร์เฟสหรือคลาสฐานนามธรรม

อย่างไรก็ตามข้อเสียคือนี่เป็นข้อมูลเฉพาะของ Json.NET สิ่ง$typeนี้จะเป็นประเภทที่มีคุณสมบัติครบถ้วนดังนั้นหากคุณกำลังต่ออนุกรมกับข้อมูลประเภท deserializer จะต้องสามารถเข้าใจได้เช่นกัน

เอกสารประกอบ: Serialization Settings with Json.NET


น่าสนใจ. ฉันจะต้องเล่นกับสิ่งนี้ เคล็ดลับดีๆ!
dthrasher

2
สำหรับ Newtonsoft.Json มันทำงานคล้ายกัน แต่คุณสมบัติคือ "$ type"
Jaap

มันง่ายเกินไป!
Shimmy Weitzhandler

1
TypeNameHandlingระวังปัญหาด้านความปลอดภัยที่เป็นไปได้ที่นี่เมื่อใช้ ดูTypeNameHandling คำเตือนใน Newtonsoft Jsonสำหรับรายละเอียด
dbc

ฉันดิ้นรนอย่างบ้าคลั่งกับตัวแปลงเมื่อวานนี้และนี่เป็นวิธีที่ดีขึ้นและเข้าใจได้ดีขึ้นขอบคุณ !!!
Horothenic

52

คุณสามารถบรรลุสิ่งนี้ได้โดยใช้คลาส JsonConverter สมมติว่าคุณมีคลาสที่มีคุณสมบัติอินเตอร์เฟส

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 ของคุณมีหน้าที่รับผิดชอบในการทำให้เป็นอนุกรมและการทำให้เป็นอนุกรมของคุณสมบัติพื้นฐาน

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

เมื่อคุณทำงานกับ Organization deserialized ผ่าน Json.Net คุณสมบัติ IPerson สำหรับเจ้าของจะเป็นประเภท Tycoon


ดีมาก. ฉันจะต้องลองใช้ตัวแปลงดู
dthrasher

4
แท็ก "[JsonConverter (typeof (TycoonConverter))]" จะยังคงใช้งานได้หรือไม่หากอยู่ในรายการอินเทอร์เฟซ
Zwik

40

แทนที่จะส่งออบเจ็กต์ JsonSerializerSettings ที่กำหนดเองไปยัง JsonConvert.SerializeObject () ด้วยอ็อพชัน TypeNameHandling.Objects ตามที่กล่าวไว้ก่อนหน้านี้คุณสามารถทำเครื่องหมายคุณสมบัติอินเทอร์เฟซเฉพาะนั้นด้วยแอ็ตทริบิวต์ดังนั้น JSON ที่สร้างขึ้นจะไม่ถูกขยายด้วยคุณสมบัติ "$ type" ในทุกวัตถุ:

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

ยอดเยี่ยม. ขอบคุณ :)
Darren Young

5
สำหรับคอลเลกชันของอินเทอร์เฟซหรือคลาสนามธรรมคุณสมบัติคือ "ItemTypeNameHandling" เช่น: [JsonProperty (ItemTypeNameHandling = TypeNameHandling.Auto)]
Anthony F

ขอบคุณสำหรับสิ่งนี้!
brudert

24

ในเวอร์ชันล่าสุดของตัวแปลง Newtonsoft Json ของบุคคลที่สามคุณสามารถตั้งค่าตัวสร้างด้วยประเภทคอนกรีตที่เกี่ยวข้องกับคุณสมบัติที่เชื่อมต่อ

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

ตราบใดที่บางสิ่งบางอย่างดำเนินการตามสิ่งนี้ควรใช้ นอกจากนี้อย่าใส่คอนสตรัคเตอร์ว่างเริ่มต้นในกรณีที่ตัวแปลง JSon พยายามใช้สิ่งนั้นคุณต้องบังคับให้ใช้ตัวสร้างที่มีชนิดคอนกรีต

ปล. นอกจากนี้ยังช่วยให้คุณตั้งค่าเป็นส่วนตัวได้


6
นี่ควรจะตะโกนจากหลังคา! จริงอยู่ที่มันเพิ่มข้อ จำกัด ในการนำไปใช้อย่างเป็นรูปธรรม แต่มันตรงไปตรงมามากกว่าวิธีอื่น ๆ สำหรับสถานการณ์เหล่านั้นที่สามารถใช้ได้
Mark Meuer

3
จะเป็นอย่างไรถ้าเรามีตัวสร้างมากกว่า 1 ตัวที่มีคอนกรีตหลายแบบมันจะยังรู้อยู่ไหม?
Teoman shipahi

1
คำตอบนี้สวยมากเมื่อเทียบกับเรื่องไร้สาระที่ซับซ้อนทั้งหมดที่คุณต้องทำอย่างอื่น นี่ควรเป็นคำตอบที่ได้รับการยอมรับ ข้อแม้อย่างหนึ่งในกรณีของฉันคือฉันต้องเพิ่ม [JsonConstructor] ก่อนตัวสร้างเพื่อให้มันทำงานได้ .... ฉันสงสัยว่าการใช้สิ่งนี้กับตัวสร้างคอนกรีตเพียงตัวเดียวของคุณจะแก้ปัญหา (อายุ 4 ปี) ของคุณได้ @Teomanshipahi
nacitar sevaht

@nacitarsevaht ฉันสามารถกลับไปแก้ไขปัญหาได้แล้ว :) อย่างไรก็ตามฉันจำไม่ได้ว่ามันคืออะไร แต่เมื่อฉันดูอีกครั้งนี่เป็นทางออกที่ดีสำหรับบางกรณี
Teoman shipahi

เราใช้สิ่งนี้ด้วย แต่ฉันชอบการแปลงในกรณีส่วนใหญ่เนื่องจากการเชื่อมต่อประเภทคอนกรีตกับตัวสร้างเอาชนะจุดประสงค์ของการใช้อินเทอร์เฟซสำหรับคุณสมบัติในตอนแรก!
gabe

19

มีปัญหาเดียวกันดังนั้นฉันจึงคิดตัวแปลงของตัวเองขึ้นมาซึ่งใช้อาร์กิวเมนต์ประเภทที่รู้จัก

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

ฉันกำหนดวิธีการขยายสองวิธีสำหรับการแยกส่วนและการทำให้เป็นอนุกรม:

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

คุณสามารถกำหนดวิธีการเปรียบเทียบและระบุประเภทของคุณเองในการแปลงฉันใช้ชื่อคลาสเท่านั้น


1
JsonConverter นี้ดีมากฉันใช้มัน แต่ประสบปัญหาสองสามอย่างที่ฉันแก้ไขด้วยวิธีนี้: - ใช้ JsonSerializer.CreateDefault () แทนเพื่อเติมข้อมูลเนื่องจากวัตถุของฉันมีลำดับชั้นที่ลึกกว่า - การใช้การสะท้อนกลับเพื่อดึงคอนสตรัคเตอร์และ Instanciate ด้วยวิธี Create ()
Aurel

3

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

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

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

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


1
นี่เป็นทางออกที่ยอดเยี่ยม ไชโย
JohnMetta

2

ฉันแค่ต้องการทำตัวอย่างที่ @Daniel T. แสดงให้เราเห็นด้านบน:

หากคุณใช้รหัสนี้เพื่อทำให้เป็นอนุกรมวัตถุของคุณ:

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

รหัสสำหรับ deserialize json ควรมีลักษณะดังนี้:

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

นี่คือวิธีที่ json สอดคล้องเมื่อใช้TypeNameHandlingแฟล็ก:ใส่คำอธิบายภาพที่นี่


-5

ฉันเคยสงสัยในสิ่งเดียวกันนี้ แต่ฉันกลัวว่าจะทำไม่ได้

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

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


1
ฉันรู้ว่ามันใช้ไม่ได้ "นอกกรอบ" แต่ฉันสงสัยว่ามีแอตทริบิวต์บางอย่างเช่น "[JsonProperty (typeof (SomethingBase))]" ที่ฉันสามารถใช้เพื่อจัดเตรียมคลาสที่เป็นรูปธรรมได้หรือไม่
dthrasher

แล้วทำไมไม่ใช้ SomethingBase แทน ISomething ในโค้ดด้านบนล่ะ? อาจเป็นที่ถกเถียงกันอยู่ว่าเรากำลังมองเรื่องนี้ในทางที่ผิดเนื่องจากไม่ควรใช้อินเทอร์เฟซในการทำให้เป็นอนุกรมเนื่องจากเพียงแค่กำหนด "อินเทอร์เฟซ" การสื่อสารกับคลาสที่กำหนด การทำให้อินเทอร์เฟซเป็นอนุกรมในทางเทคนิคเป็นเรื่องไร้สาระเช่นเดียวกับการทำให้เป็นอนุกรมของคลาสนามธรรม ดังนั้นในขณะที่ "ทำได้" ฉันขอเถียงว่า "ไม่ควรทำ"
Timothy Baldridge

คุณได้ดูคลาสใด ๆ ใน Newtonsoft.Json.Serialization Namespace แล้วหรือยัง? โดยเฉพาะคลาส JsonObjectContract?
จอห์นนี่

-9

นี่คือการอ้างอิงถึงบทความที่เขียนโดย ScottGu

จากนั้นฉันจึงเขียนโค้ดบางอย่างซึ่งคิดว่าอาจเป็นประโยชน์

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

และนี่คือวิธีที่คุณจะเรียกมัน

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}

ถ้าฉันเข้าใจถูกต้องฉันไม่คิดว่าคุณจะต้องระบุคลาสคอนกรีตที่ใช้อินเทอร์เฟซสำหรับการทำให้อนุกรม JSON


1
ตัวอย่างของคุณใช้ JavaScriptSerializer ซึ่งเป็นคลาสใน. NET Framework ฉันใช้ Json.NET เป็นซีเรียลไลเซอร์ codeplex.com/Json
dthrasher

3
ไม่ได้อ้างถึงคำถามดั้งเดิม Json.NET ถูกกล่าวถึงอย่างชัดเจนที่นั่น
Oliver
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.