JSON.NET 反序列化 - 单个结果与数组

blg*_*boy 4 c# serialization json json.net deserialization

我在尝试确定如何使我的序列化能够正确访问单个结果以及数组时遇到困难。

当我进行 REST 调用在服务器上查找某些内容时,有时它会返回模型数组,但如果搜索结果只有一个模型,则不会作为错误返回。这是当我收到无法反序列化的异常时,因为对象属性需要一个数组,但接收的是单个对象。

有没有办法定义我的类,以便它可以在返回时处理类型为 ns1.models 的单个对象而不是对象数组?

[JsonObject]
public class Responses
{
    [JsonProperty(PropertyName = "ns1.model")]
    public List<Model> Model { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

可反序列化的响应:

{"ns1.model":[
  {"@mh":"0x20e800","ns1.attribute":{"@id":"0x1006e","$":"servername"}},
  {"@mh":"0x21a400","ns1.attribute":{"@id":"0x1006e","$":"servername2"}}
]}
Run Code Online (Sandbox Code Playgroud)

无法序列化的响应(因为 JSON 仅包含单个“ns1.model”):

{"ns1.model":
   {"@mh":"0x20e800","ns1.attribute":{"@id":"0x1006e","$":"servername"}}
}
Run Code Online (Sandbox Code Playgroud)

例外:

Newtonsoft.Json.JsonSerializationException was unhandled   HResult=-2146233088   Message=Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'System.Collections.Generic.List`1[ConsoleApplication1.Model]' because the type requires a JSON array (e.g. [1,2,3]) to deserialize correctly. To fix this error either change the JSON to a JSON array (e.g. [1,2,3]) or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path '['ns1.model-response-list'].['ns1.model-responses'].['ns1.model'].@mh', line 1, position 130
Run Code Online (Sandbox Code Playgroud)

Rob*_*o B 5

要处理这个问题,您必须使用自定义 JsonConverter。但你可能已经想到了这一点。您只是在寻找一个可以立即使用的转换器。这不仅仅为所描述的情况提供了一个解决方案。我举一个例子并提出问题。

如何使用我的转换器:

将 JsonConverter 属性放置在属性上方。JsonConverter(typeof(SafeCollectionConverter))

public class Response
{
    [JsonProperty("ns1.model")]
    [JsonConverter(typeof(SafeCollectionConverter))]
    public List<Model> Model { get; set; }
}

public class Model
{
    [JsonProperty("@mh")]
    public string Mh { get; set; }

    [JsonProperty("ns1.attribute")]
    public ModelAttribute Attribute { get; set; }
}

public class ModelAttribute
{
    [JsonProperty("@id")]
    public string Id { get; set; }

    [JsonProperty("$")]
    public string Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这是我的转换器:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

该转换器使用以下类:

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

它到底有什么作用?如果放置转换器属性,转换器将用于此属性。如果您期望 json 数组的结果为 1 或没有结果,则可以在普通对象上使用它。IEnumerable或者您在需要 json 对象或 json 数组的地方使用它。(知道array- object[]- 是一个IEnumerable)一个缺点是这个转换器只能放在一个属性之上,因为他认为他可以转换一切。并予以警告。Astring也是一个IEnumerable.

它提供的不仅仅是这个问题的答案:如果您通过 id 搜索某些内容,您知道您将得到一个包含一个结果或没有结果的数组。这ToObjectCollectionSafe<TResult>()方法可以为您处理这个问题。

这可用于使用 JSON.net 的单一结果与数组,并处理同一属性的单个项目和数组,并且可以将数组转换为单个对象。

我为带有过滤器的服务器上的 REST 请求做了此操作,该过滤器在数组中返回一个结果,但希望将结果作为代码中的单个对象返回。也适用于 OData 结果响应,其中包含数组中的一项的扩展结果。

玩得开心。