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

本文关键字:结果 数组 单个 NET 反序列化 JSON | 更新日期: 2023-09-27 18:31:12

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

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

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

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

可以反序列化的响应:

{"ns1.model":[
  {"@mh":"0x20e800","ns1.attribute":{"@id":"0x1006e","$":"servername"}},
  {"@mh":"0x21a400","ns1.attribute":{"@id":"0x1006e","$":"servername2"}}
]}

无法序列化的响应(因为 JSON 只包含一个"ns1.model"):

{"ns1.model":
   {"@mh":"0x20e800","ns1.attribute":{"@id":"0x1006e","$":"servername"}}
}

例外:

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

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

要处理这个问题,你必须使用自定义的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; }
}

这是我的转换器:

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 或没有结果,则可以在普通对象上使用它。或者,您可以在需要 json 对象或 json 数组的IEnumerable上使用它。(知道array - object[] - 是一个IEnumerable )缺点是这个转换器只能放在属性上方,因为他认为他可以转换所有内容。并被警告string也是IEnumerable.

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

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

我为服务器上的 REST 请求做了这个,该过滤器在数组中返回一个结果,但希望将结果作为我的代码中的单个对象返回。以及具有扩展结果的 OData 结果响应,其中包含数组中的一个项目。

玩得开心。

我想你的问题已经回答了。请看一下这个线程:如何使用 JSON.net 处理同一属性的单个项目和数组。

基本上,这样做的方法是为您的属性定义一个自定义的 JsonConvertor。

在当前版本的 JSON.NET 中,您的问题没有一个优雅的解决方案。您必须编写自定义解析代码来处理这个问题。

正如@boyomarinov所说,您可以开发一个自定义转换器,但由于您的 JSON 非常简单,您只需将 JSON 解析为一个对象,然后像这样处理这两种情况:

var obj = JObject.Parse(json);
var responses = new Responses { Model = new List<Model>() };
foreach (var child in obj.Values())
{
    if (child is JArray)
    {
        responses.Model = child.ToObject<List<Model>>();
        break;
    }
    else
        responses.Model.Add(child.ToObject<Model>());
}

使用JRaw类型代理属性ModelRaw

public class Responses
{
    [JsonIgnore]
    public List<Model> Model { get; set; }
    [JsonProperty(PropertyName = "ns1.model")]
    public JRaw ModelRaw
    {
        get { return new JRaw(JsonConvert.SerializeObject(Model)); }
        set
        {
            var raw = value.ToString(Formatting.None);
            Model = raw.StartsWith("[")
                ? JsonConvert.DeserializeObject<List<Model>>(raw)
                : new List<Model> { JsonConvert.DeserializeObject<Model>(raw) };
        }
    }
}