【问题标题】:JSON.NET Deserialization - Single Result vs ArrayJSON.NET 反序列化 - 单个结果与数组
【发布时间】:2015-01-18 19:16:10
【问题描述】:

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

当我在服务器上进行 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

【问题讨论】:

标签: c# json serialization json.net deserialization


【解决方案1】:

要处理此问题,您必须使用自定义 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 或没有结果,则可以在普通对象上使用它。或者你在一个IEnumerable 上使用它,你期望一个 json 对象或 json 数组。 (知道array -object[]- 是IEnumerable) 一个缺点是这个转换器只能放在一个属性之上,因为他认为他可以转换所有东西。并被警告string 也是 IEnumerable

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

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

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

玩得开心。

【讨论】:

    【解决方案2】:

    我认为您的问题已经得到解答。请看一下这个线程: How to handle both a single item and an array for the same property using JSON.net.

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

    【讨论】:

      【解决方案3】:

      在当前版本的 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>());
      
      }
      

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2019-05-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多