วิธีจัดการทั้งรายการเดียวและอาร์เรย์สำหรับคุณสมบัติเดียวกันโดยใช้ JSON.net


108

ฉันกำลังพยายามแก้ไขไลบรารี SendGridPlus เพื่อจัดการกับเหตุการณ์ SendGrid แต่ฉันมีปัญหากับการจัดการหมวดหมู่ที่ไม่สอดคล้องกันใน API

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

[
  {
    "email": "john.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": [
      "newuser",
      "transactional"
    ],
    "event": "open"
  },
  {
    "email": "jane.doe@sendgrid.com",
    "timestamp": 1337966815,
    "category": "olduser",
    "event": "open"
  }
]

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

มีวิธีอื่นอีกไหมที่ฉันสามารถจัดการกับสิ่งนี้โดยใช้ Json.Net

คำตอบ:


211

JsonConverterวิธีที่ดีที่สุดเพื่อจัดการสถานการณ์นี้คือการใช้ที่กำหนดเอง

ก่อนที่เราจะไปที่ตัวแปลงเราจะต้องกำหนดคลาสเพื่อแยกข้อมูลลงในข้อมูล สำหรับCategoriesคุณสมบัติที่สามารถแตกต่างกันระหว่างรายการเดียวและอาร์เรย์ให้กำหนดเป็น a List<string>และทำเครื่องหมายด้วย [JsonConverter]แอตทริบิวต์เพื่อให้ JSON.Net รู้ว่าจะใช้ตัวแปลงที่กำหนดเองสำหรับคุณสมบัตินั้น ฉันขอแนะนำให้ใช้[JsonProperty]แอตทริบิวต์เพื่อให้คุณสมบัติสมาชิกสามารถตั้งชื่อที่มีความหมายได้โดยไม่ขึ้นกับสิ่งที่กำหนดไว้ใน JSON

class Item
{
    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("timestamp")]
    public int Timestamp { get; set; }

    [JsonProperty("event")]
    public string Event { get; set; }

    [JsonProperty("category")]
    [JsonConverter(typeof(SingleOrArrayConverter<string>))]
    public List<string> Categories { get; set; }
}

นี่คือวิธีที่ฉันจะใช้ตัวแปลง สังเกตว่าฉันได้สร้างตัวแปลงเป็นแบบทั่วไปเพื่อให้สามารถใช้กับสตริงหรือวัตถุประเภทอื่น ๆ ได้ตามต้องการ

class SingleOrArrayConverter<T> : JsonConverter
{
    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof(List<T>));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        JToken token = JToken.Load(reader);
        if (token.Type == JTokenType.Array)
        {
            return token.ToObject<List<T>>();
        }
        return new List<T> { token.ToObject<T>() };
    }

    public override bool CanWrite
    {
        get { return false; }
    }

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

นี่คือโปรแกรมสั้น ๆ ที่สาธิตการใช้งานตัวแปลงกับข้อมูลตัวอย่างของคุณ:

class Program
{
    static void Main(string[] args)
    {
        string json = @"
        [
          {
            ""email"": ""john.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": [
              ""newuser"",
              ""transactional""
            ],
            ""event"": ""open""
          },
          {
            ""email"": ""jane.doe@sendgrid.com"",
            ""timestamp"": 1337966815,
            ""category"": ""olduser"",
            ""event"": ""open""
          }
        ]";

        List<Item> list = JsonConvert.DeserializeObject<List<Item>>(json);

        foreach (Item obj in list)
        {
            Console.WriteLine("email: " + obj.Email);
            Console.WriteLine("timestamp: " + obj.Timestamp);
            Console.WriteLine("event: " + obj.Event);
            Console.WriteLine("categories: " + string.Join(", ", obj.Categories));
            Console.WriteLine();
        }
    }
}

และสุดท้ายนี่คือผลลัพธ์ของด้านบน:

email: john.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: newuser, transactional

email: jane.doe@sendgrid.com
timestamp: 1337966815
event: open
categories: olduser

ซอ: https://dotnetfiddle.net/lERrmu

แก้ไข

หากคุณต้องการใช้วิธีอื่นนั่นคือทำให้เป็นอนุกรมในขณะที่ยังคงรูปแบบเดิมไว้คุณสามารถใช้WriteJson()วิธีการของตัวแปลงดังที่แสดงด้านล่าง (อย่าลืมลบการCanWriteลบล้างหรือเปลี่ยนเพื่อส่งคืนtrueมิฉะนั้นWriteJson()จะไม่ถูกเรียกใช้)

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        List<T> list = (List<T>)value;
        if (list.Count == 1)
        {
            value = list[0];
        }
        serializer.Serialize(writer, value);
    }

ซอ: https://dotnetfiddle.net/XG3eRy


5
สมบูรณ์แบบ! คุณคือผู้ชาย โชคดีที่ฉันได้ทำสิ่งอื่น ๆ ทั้งหมดเกี่ยวกับการใช้ JsonProperty เพื่อทำให้คุณสมบัติมีความหมายมากขึ้น ขอบคุณสำหรับคำตอบที่สมบูรณ์อย่างน่าอัศจรรย์ :)
Robert McLaws

ไม่มีปัญหา; ดีใจที่พบว่ามีประโยชน์
Brian Rogers

1
ยอดเยี่ยม! นี่คือสิ่งที่ฉันกำลังมองหา @BrianRogers ถ้าคุณเคยอยู่ในอัมสเตอร์ดัมเครื่องดื่มอยู่กับฉัน!
Mad Dog Tannen

2
@israelaltar คุณไม่จำเป็นต้องเพิ่มตัวแปลงในการDeserializeObjectโทรหากคุณใช้[JsonConverter]แอตทริบิวต์ในคุณสมบัติรายการในคลาสของคุณดังที่แสดงในคำตอบด้านบน หากคุณไม่ได้DeserializeObjectใช้แอตทริบิวต์แล้วใช่คุณจะต้องผ่านการแปลงเพื่อ
Brian Rogers

1
@ShaunLangley ในการทำให้ตัวแปลงใช้อาร์เรย์แทนรายการให้เปลี่ยนการอ้างอิงทั้งหมดเป็นList<T>ในตัวแปลงเป็นT[]และเปลี่ยน.Countเป็น.Length. dotnetfiddle.net/vnCNgZ
Brian Rogers

7

ฉันทำงานนี้มานานแล้วและขอบคุณ Brian สำหรับคำตอบของเขา ทั้งหมดที่ฉันเพิ่มคือคำตอบ vb.net!:

Public Class SingleValueArrayConverter(Of T)
sometimes-array-and-sometimes-object
    Inherits JsonConverter
    Public Overrides Sub WriteJson(writer As JsonWriter, value As Object, serializer As JsonSerializer)
        Throw New NotImplementedException()
    End Sub

    Public Overrides Function ReadJson(reader As JsonReader, objectType As Type, existingValue As Object, serializer As JsonSerializer) As Object
        Dim retVal As Object = New [Object]()
        If reader.TokenType = JsonToken.StartObject Then
            Dim instance As T = DirectCast(serializer.Deserialize(reader, GetType(T)), T)
            retVal = New List(Of T)() From { _
                instance _
            }
        ElseIf reader.TokenType = JsonToken.StartArray Then
            retVal = serializer.Deserialize(reader, objectType)
        End If
        Return retVal
    End Function
    Public Overrides Function CanConvert(objectType As Type) As Boolean
        Return False
    End Function
End Class

จากนั้นในชั้นเรียนของคุณ:

 <JsonProperty(PropertyName:="JsonName)> _
 <JsonConverter(GetType(SingleValueArrayConverter(Of YourObject)))> _
    Public Property YourLocalName As List(Of YourObject)

หวังว่านี่จะช่วยคุณประหยัดเวลาได้บ้าง


Typos: <JsonConverter (GetType (SingleValueArrayConverter (ของ YourObject)))> _ ทรัพย์สินสาธารณะ YourLocalName เป็นรายการ (ของ YourObject)
GlennG

3

ในฐานะที่เป็นรูปแบบเล็กน้อยสำหรับคำตอบที่ยอดเยี่ยมของBrian Rogersนี่คือสองเวอร์ชันที่ได้รับการปรับแต่งของSingleOrArrayConverter<T>.

ประการแรกนี่คือเวอร์ชันที่ใช้ได้List<T>กับทุกประเภทTที่ไม่ใช่คอลเลกชัน:

public class SingleOrArrayListConverter : JsonConverter
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to /programming/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;
    readonly IContractResolver resolver;

    public SingleOrArrayListConverter() : this(false) { }

    public SingleOrArrayListConverter(bool canWrite) : this(canWrite, null) { }

    public SingleOrArrayListConverter(bool canWrite, IContractResolver resolver)
    {
        this.canWrite = canWrite;
        // Use the global default resolver if none is passed in.
        this.resolver = resolver ?? new JsonSerializer().ContractResolver;
    }

    static bool CanConvert(Type objectType, IContractResolver resolver)
    {
        Type itemType;
        JsonArrayContract contract;
        return CanConvert(objectType, resolver, out itemType, out contract);
    }

    static bool CanConvert(Type objectType, IContractResolver resolver, out Type itemType, out JsonArrayContract contract)
    {
        if ((itemType = objectType.GetListItemType()) == null)
        {
            itemType = null;
            contract = null;
            return false;
        }
        // Ensure that [JsonObject] is not applied to the type.
        if ((contract = resolver.ResolveContract(objectType) as JsonArrayContract) == null)
            return false;
        var itemContract = resolver.ResolveContract(itemType);
        // Not implemented for jagged arrays.
        if (itemContract is JsonArrayContract)
            return false;
        return true;
    }

    public override bool CanConvert(Type objectType) { return CanConvert(objectType, resolver); }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        Type itemType;
        JsonArrayContract contract;

        if (!CanConvert(objectType, serializer.ContractResolver, out itemType, out contract))
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), objectType));
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (IList)(existingValue ?? contract.DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Add<T> method.
            list.Add(serializer.Deserialize(reader, itemType));
        return list;
    }

    public override bool CanWrite { get { return canWrite; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        var list = value as ICollection;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        // Here we take advantage of the fact that List<T> implements IList to avoid having to use reflection to call the generic Count method.
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}

public static partial class JsonExtensions
{
    public static JsonReader MoveToContent(this JsonReader reader)
    {
        while ((reader.TokenType == JsonToken.Comment || reader.TokenType == JsonToken.None) && reader.Read())
            ;
        return reader;
    }

    internal static Type GetListItemType(this Type type)
    {
        // Quick reject for performance
        if (type.IsPrimitive || type.IsArray || type == typeof(string))
            return null;
        while (type != null)
        {
            if (type.IsGenericType)
            {
                var genType = type.GetGenericTypeDefinition();
                if (genType == typeof(List<>))
                    return type.GetGenericArguments()[0];
            }
            type = type.BaseType;
        }
        return null;
    }
}

สามารถใช้งานได้ดังนี้:

var settings = new JsonSerializerSettings
{
    // Pass true if you want single-item lists to be reserialized as single items
    Converters = { new SingleOrArrayListConverter(true) },
};
var list = JsonConvert.DeserializeObject<List<Item>>(json, settings);

หมายเหตุ:

  • ตัวแปลงหลีกเลี่ยงความจำเป็นในการโหลดค่า JSON ล่วงหน้าทั้งหมดลงในหน่วยความจำตามJTokenลำดับชั้น

  • ตัวแปลงใช้ไม่ได้กับรายการที่มีการจัดลำดับรายการเป็นคอลเลกชันเช่น List<string []>

  • canWriteอาร์กิวเมนต์บูลีนที่ส่งผ่านไปยังตัวสร้างจะควบคุมว่าจะทำให้รายการองค์ประกอบเดี่ยวซ้ำเป็นค่า JSON หรือเป็นอาร์เรย์ JSON

  • ตัวแปลงReadJson()ใช้existingValueif ที่จัดสรรไว้ล่วงหน้าเพื่อรองรับการเติมสมาชิกรายการรับอย่างเดียว

ประการที่สองนี่คือเวอร์ชันที่ทำงานร่วมกับคอลเล็กชันทั่วไปอื่น ๆ เช่นObservableCollection<T>:

public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to /programming/18994685/how-to-handle-both-a-single-item-and-an-array-for-the-same-property-using-json-n
    // by Brian Rogers https://stackoverflow.com/users/10263/brian-rogers
    readonly bool canWrite;

    public SingleOrArrayCollectionConverter() : this(false) { }

    public SingleOrArrayCollectionConverter(bool canWrite) { this.canWrite = canWrite; }

    public override bool CanConvert(Type objectType)
    {
        return typeof(TCollection).IsAssignableFrom(objectType);
    }

    static void ValidateItemContract(IContractResolver resolver)
    {
        var itemContract = resolver.ResolveContract(typeof(TItem));
        if (itemContract is JsonArrayContract)
            throw new JsonSerializationException(string.Format("Item contract type {0} not supported.", itemContract));
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        if (reader.MoveToContent().TokenType == JsonToken.Null)
            return null;
        var list = (ICollection<TItem>)(existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator());
        if (reader.TokenType == JsonToken.StartArray)
            serializer.Populate(reader, list);
        else
            list.Add(serializer.Deserialize<TItem>(reader));
        return list;
    }

    public override bool CanWrite { get { return canWrite; } }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        ValidateItemContract(serializer.ContractResolver);
        var list = value as ICollection<TItem>;
        if (list == null)
            throw new JsonSerializationException(string.Format("Invalid type for {0}: {1}", GetType(), value.GetType()));
        if (list.Count == 1)
        {
            foreach (var item in list)
            {
                serializer.Serialize(writer, item);
                break;
            }
        }
        else
        {
            writer.WriteStartArray();
            foreach (var item in list)
                serializer.Serialize(writer, item);
            writer.WriteEndArray();
        }
    }
}

แล้วถ้ารุ่นของคุณใช้การพูดเป็นObservableCollection<T>บางTคุณสามารถใช้มันเป็นดังนี้:

class Item
{
    public string Email { get; set; }
    public int Timestamp { get; set; }
    public string Event { get; set; }

    [JsonConverter(typeof(SingleOrArrayCollectionConverter<ObservableCollection<string>, string>))]
    public ObservableCollection<string> Category { get; set; }
}

หมายเหตุ:

  • นอกจากนี้ในการบันทึกและข้อ จำกัด สำหรับSingleOrArrayListConverterการTCollectionประเภทต้องได้รับการอ่าน / เขียนและมี constructor parameterless

ซอสาธิตกับหน่วยพื้นฐานการทดสอบที่นี่


0

ฉันมีปัญหาที่คล้ายกันมาก คำขอ Json ของฉันไม่เป็นที่รู้จักสำหรับฉัน ฉันรู้เพียง

จะมี objectId อยู่ในนั้นและคู่ค่าคีย์ที่ไม่ระบุตัวตนและอาร์เรย์

ฉันใช้สำหรับโมเดล EAV ที่ฉันทำ:

คำขอ JSON ของฉัน:

{objectId ": 2," firstName ":" Hans "," email ": [" a@b.de "," a@c.de "]," name ":" Andre "," something ": [" 232 "," 123 "]}

ชั้นเรียนของฉันฉันกำหนด:

[JsonConverter(typeof(AnonyObjectConverter))]
public class AnonymObject
{
    public AnonymObject()
    {
        fields = new Dictionary<string, string>();
        list = new List<string>();
    }

    public string objectid { get; set; }
    public Dictionary<string, string> fields { get; set; }
    public List<string> list { get; set; }
}

และตอนนี้ฉันต้องการยกเลิกการกำหนดค่าแอตทริบิวต์ที่ไม่รู้จักด้วยค่าและอาร์เรย์ในนั้น Converter ของฉันดูเหมือนว่า:

   public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        AnonymObject anonym = existingValue as AnonymObject ?? new AnonymObject();
        bool isList = false;
        StringBuilder listValues = new StringBuilder();

        while (reader.Read())
        {
            if (reader.TokenType == JsonToken.EndObject) continue;

            if (isList)
            {
                while (reader.TokenType != JsonToken.EndArray)
                {
                    listValues.Append(reader.Value.ToString() + ", ");

                    reader.Read();
                }
                anonym.list.Add(listValues.ToString());
                isList = false;

                continue;
            }

            var value = reader.Value.ToString();

            switch (value.ToLower())
            {
                case "objectid":
                    anonym.objectid = reader.ReadAsString();
                    break;
                default:
                    string val;

                    reader.Read();
                    if(reader.TokenType == JsonToken.StartArray)
                    {
                        isList = true;
                        val = "ValueDummyForEAV";
                    }
                    else
                    {
                        val = reader.Value.ToString();
                    }
                    try
                    {
                        anonym.fields.Add(value, val);
                    }
                    catch(ArgumentException e)
                    {
                        throw new ArgumentException("Multiple Attribute found");
                    }
                    break;
            }

        }

        return anonym;
    }

ดังนั้นทุกครั้งที่ฉันได้รับ AnonymObject ฉันสามารถทำซ้ำผ่านพจนานุกรมและทุกครั้งที่มีการตั้งค่าสถานะ "ValueDummyForEAV" ของฉันฉันจะเปลี่ยนเป็นรายการอ่านบรรทัดแรกและแยกค่า หลังจากนั้นฉันจะลบรายการแรกออกจากรายการและดำเนินการซ้ำจากพจนานุกรม

อาจมีคนมีปัญหาเดียวกันและสามารถใช้สิ่งนี้ได้ :)

ขอแสดงความนับถือ Andre


0

คุณสามารถใช้ a JSONConverterAttributeได้ที่นี่: http://james.newtonking.com/projects/json/help/

สมมติว่าคุณมีคลาสที่ดูเหมือน

public class RootObject
{
    public string email { get; set; }
    public int timestamp { get; set; }
    public string smtpid { get; set; }
    public string @event { get; set; }
    public string category[] { get; set; }
}

คุณจะตกแต่งคุณสมบัติหมวดหมู่ตามที่เห็นที่นี่:

    [JsonConverter(typeof(SendGridCategoryConverter))]
    public string category { get; set; }

public class SendGridCategoryConverter : JsonConverter
{
  public override bool CanConvert(Type objectType)
  {
    return true; // add your own logic
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
   // do work here to handle returning the array regardless of the number of objects in 
  }

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

ขอบคุณสำหรับสิ่งนี้ แต่ก็ยังไม่สามารถแก้ไขปัญหาได้ เมื่ออาร์เรย์จริงเข้ามามันยังคงแสดงข้อผิดพลาดก่อนที่โค้ดของฉันจะสามารถดำเนินการกับออบเจ็กต์ที่มีอาร์เรย์จริงได้ 'ข้อมูลเพิ่มเติม: โทเค็นที่ไม่คาดคิดเมื่อ deserializing object: String เส้นทาง '[2] .category [0]', บรรทัดที่ 17 ตำแหน่ง 27 '
Robert McLaws

+ "\" เหตุการณ์ \ ": \" ประมวลผล \ ", \ n" + "} \ n" + "]";
Robert McLaws

มันประมวลผลออบเจ็กต์แรกได้ดีและจัดการกับอาร์เรย์ได้อย่างสวยงาม แต่เมื่อฉันสร้างอาร์เรย์สำหรับออบเจ็กต์ที่ 2 มันล้มเหลว
Robert McLaws

@AdvancedREI โดยไม่เห็นรหัสของคุณฉันเดาว่าคุณกำลังปล่อยให้เครื่องอ่านอยู่ในตำแหน่งที่ไม่ถูกต้องหลังจากที่คุณอ่าน JSON แทนที่จะพยายามใช้เครื่องอ่านโดยตรงควรโหลดวัตถุ JToken จากเครื่องอ่านแล้วไปจากที่นั่น ดูคำตอบของฉันสำหรับการใช้งานตัวแปลง
Brian Rogers

รายละเอียดที่ดีกว่ามากในคำตอบของ Brian ใช้อันนั้นสิ :)
Tim Gabrhel

0

ในการจัดการสิ่งนี้คุณต้องใช้ JsonConverter ที่กำหนดเอง แต่คุณอาจมีสิ่งนั้นอยู่ในใจแล้ว คุณกำลังมองหาตัวแปลงที่ใช้งานได้ทันที และนี่เป็นมากกว่าวิธีแก้ปัญหาสำหรับสถานการณ์ที่อธิบายไว้ ฉันยกตัวอย่างกับคำถามที่ถาม

วิธีใช้ตัวแปลงของฉัน:

วางแอตทริบิวต์ JsonConverter เหนือคุณสมบัติ JsonConverter(typeof(SafeCollectionConverter))

public class SendGridEvent
{
    [JsonProperty("email")]
    public string Email { get; set; }

    [JsonProperty("timestamp")]
    public long Timestamp { get; set; }

    [JsonProperty("category"), JsonConverter(typeof(SafeCollectionConverter))]
    public string[] Category { get; set; }

    [JsonProperty("event")]
    public string Event { get; set; }
}

และนี่คือตัวแปลงของฉัน:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;

namespace stackoverflow.question18994685
{
    public class SafeCollectionConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return true;
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            //This not works for Populate (on existingValue)
            return serializer.Deserialize<JToken>(reader).ToObjectCollectionSafe(objectType, serializer);
        }     

        public override bool CanWrite => false;

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

และตัวแปลงนี้ใช้คลาสต่อไปนี้:

using System;

namespace Newtonsoft.Json.Linq
{
    public static class SafeJsonConvertExtensions
    {
        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType)
        {
            return ToObjectCollectionSafe(jToken, objectType, JsonSerializer.CreateDefault());
        }

        public static object ToObjectCollectionSafe(this JToken jToken, Type objectType, JsonSerializer jsonSerializer)
        {
            var expectArray = typeof(System.Collections.IEnumerable).IsAssignableFrom(objectType);

            if (jToken is JArray jArray)
            {
                if (!expectArray)
                {
                    //to object via singel
                    if (jArray.Count == 0)
                        return JValue.CreateNull().ToObject(objectType, jsonSerializer);

                    if (jArray.Count == 1)
                        return jArray.First.ToObject(objectType, jsonSerializer);
                }
            }
            else if (expectArray)
            {
                //to object via JArray
                return new JArray(jToken).ToObject(objectType, jsonSerializer);
            }

            return jToken.ToObject(objectType, jsonSerializer);
        }

        public static T ToObjectCollectionSafe<T>(this JToken jToken)
        {
            return (T)ToObjectCollectionSafe(jToken, typeof(T));
        }

        public static T ToObjectCollectionSafe<T>(this JToken jToken, JsonSerializer jsonSerializer)
        {
            return (T)ToObjectCollectionSafe(jToken, typeof(T), jsonSerializer);
        }
    }
}

มันทำอะไรกันแน่? หากคุณวางแอตทริบิวต์ตัวแปลงตัวแปลงจะถูกใช้สำหรับคุณสมบัตินี้ คุณสามารถใช้มันบนวัตถุปกติหากคุณคาดหวังว่าอาร์เรย์ json ที่มี 1 หรือไม่มีผลลัพธ์ หรือคุณใช้ในIEnumerableตำแหน่งที่คุณคาดหวังว่าวัตถุ json หรืออาร์เรย์ json (รู้ว่าarray- object[]- คือข้อIEnumerable) ข้อเสียคือตัวแปลงนี้สามารถวางไว้เหนือคุณสมบัติเท่านั้นเพราะเขาคิดว่าเขาสามารถแปลงทุกอย่างได้ และได้รับการเตือน A stringยังเป็นไฟล์IEnumerableไฟล์.

และมีมากกว่าคำตอบสำหรับคำถาม: หากคุณค้นหาบางสิ่งด้วย id คุณจะรู้ว่าคุณจะได้รับอาร์เรย์กลับมาโดยมีผลลัพธ์เดียวหรือไม่มีเลย ToObjectCollectionSafe<TResult>()วิธีการสามารถจัดการเพื่อคุณ

สิ่งนี้สามารถใช้ได้กับ Single Result vs Array โดยใช้ JSON.net และจัดการทั้งรายการเดียวและอาร์เรย์สำหรับคุณสมบัติเดียวกันและสามารถแปลงอาร์เรย์เป็นวัตถุเดียวได้

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

ขอให้สนุกกับมัน


-2

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

โปรดดูว่าคุณมีเวลาและบอกฉันว่าคุณคิดอย่างไร https://github.com/MarcelloCarreira/sendgrid-csharp-eventwebhook

มันขึ้นอยู่กับการแก้ปัญหาที่https://sendgrid.com/blog/tracking-email-using-azure-sendgrid-event-webhook-part-1/แต่ฉันยังเพิ่มการแปลงวันที่จากการประทับเวลาอัปเกรดตัวแปรเพื่อแสดง โมเดล SendGrid ปัจจุบัน (และทำให้หมวดหมู่ทำงานได้)

ฉันยังสร้างตัวจัดการที่มีการตรวจสอบสิทธิ์พื้นฐานเป็นตัวเลือก ดูไฟล์ ashx และตัวอย่าง

ขอบคุณ!

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