Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/jsp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何使用JSON.net处理同一属性的单个项和数组_Json.net_Sendgrid - Fatal编程技术网

如何使用JSON.net处理同一属性的单个项和数组

如何使用JSON.net处理同一属性的单个项和数组,json.net,sendgrid,Json.net,Sendgrid,我正试图修复我的SendGridPlus库以处理SendGrid事件,但由于API中对类别的处理不一致,我遇到了一些问题 在以下来自的有效负载示例中,您会注意到每个项目的category属性可以是单个字符串或字符串数组 [ { "email": "john.doe@sendgrid.com", "timestamp": 1337966815, "category": [ "newuser", "transactional" ],

我正试图修复我的SendGridPlus库以处理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是否还有其他方法可以处理此问题?

您可以使用
JSONConverterAttribute
,如下所示:

假设你有一个看起来像

公共类根对象
{
公共字符串电子邮件{get;set;}
公共int时间戳{get;set;}
公共字符串smtpid{get;set;}
公共字符串@event{get;set;}
公共字符串类别[]{get;set;}
}
您将装饰类别属性,如下所示:

[JsonConverter(typeof(SendGridCategoryConverter))]
公共字符串类别{get;set;}
公共类SendGridCategoryConverter:JsonConverter
{
公共覆盖布尔CanConvert(类型objectType)
{
返回true;//添加您自己的逻辑
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
//请在此处执行操作,以处理返回数组的问题,而不考虑数组中的对象数
}
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
//留给读者作为练习:)
抛出新的NotImplementedException();
}
}

处理这种情况的最佳方法是使用自定义的

在进入转换器之前,我们需要定义一个类来反序列化数据。对于单个项和数组之间可能不同的
类别
属性,请将其定义为
列表
,并使用
[JsonConverter]
属性对其进行标记,以便JSON.Net知道如何为该属性使用自定义转换器。我还建议使用
[JsonProperty]
属性,这样就可以为成员属性指定有意义的名称,而不依赖于JSON中定义的内容

类项目
{
[JsonProperty(“电子邮件”)]
公共字符串电子邮件{get;set;}
[JsonProperty(“时间戳”)]
公共int时间戳{get;set;}
[JsonProperty(“事件”)]
公共字符串事件{get;set;}
[JsonProperty(“类别”)]
[JsonConverter(类型(SingleOrArrayConverter))]
公共列表类别{get;set;}
}
下面是我将如何实现转换器。请注意,我已将转换器设置为通用的,以便可以根据需要与字符串或其他类型的对象一起使用

class SingleOrArrayConverter:JsonConverter
{
公共覆盖布尔CanConvert(类型objectType)
{
返回(objectType==typeof(List));
}
公共重写对象ReadJson(JsonReader阅读器,类型objectType,对象existingValue,JsonSerializer序列化程序)
{
JToken令牌=JToken.Load(读卡器);
if(token.Type==JTokenType.Array)
{
返回token.ToObject();
}
返回新列表{token.ToObject()};
}
公共覆盖布尔可写
{
获取{return false;}
}
公共重写void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
抛出新的NotImplementedException();
}
}
下面是一个简短的程序,用示例数据演示转换器的工作:

类程序
{
静态void Main(字符串[]参数)
{
字符串json=@”
[
{
“电子邮件:”约翰。doe@sendgrid.com"",
“时间戳”:133796815,
“类别”:[
“新用户”,
“事务性”
],
“事件”:“打开”
},
{
“电子邮件:”简。doe@sendgrid.com"",
“时间戳”:133796815,
“”类别“”:“”旧用户“”,
“事件”:“打开”
}
]";
List List=JsonConvert.DeserializeObject(json);
foreach(列表中的项目obj)
{
Console.WriteLine(“电子邮件:+obj.email”);
Console.WriteLine(“时间戳:+obj.timestamp”);
控制台写入线(“事件:+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
小提琴:

编辑

如果需要采用另一种方式,即序列化,同时保持相同的格式,则可以实现转换器的
WriteJson()
方法,如下所示。(请确保删除
CanWrite
覆盖或将其更改为返回
true
,否则将永远不会调用
WriteJson()

public override void WriteJson(JsonWriter编写器、对象值、JsonSerializer序列化器)
{
列表=(列表)值;
如果(list.Count==1)
{
值=列表[0];
}
serializer.Serialize(writer,value);
}

Fiddle:

我找到了另一个解决方案,可以使用object将类别处理为字符串或数组。这样我就不需要搞乱json序列化程序

如果你有时间,请看一下,告诉我你的想法

它基于当时的解决方案,但我还添加了时间戳的日期转换,升级了变量以反映当前的SendGrid模型(并使类别起作用)

我还创建了一个带有basicauth选项的处理程序。请参阅ashx文件和示例


谢谢大家!

我在这方面工作了很久,感谢布赖恩的回答。 A.
public class SingleOrArrayListConverter : JsonConverter
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to https://stackoverflow.com/questions/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);
public class SingleOrArrayCollectionConverter<TCollection, TItem> : JsonConverter
    where TCollection : ICollection<TItem>
{
    // Adapted from this answer https://stackoverflow.com/a/18997172
    // to https://stackoverflow.com/questions/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();
        }
    }
}
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; }
}
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);
        }
    }
}