【问题标题】:Deserialize a json field with different data types without using Newtonsoft json but with System.Web.Script.Serialization.JavaScriptSerializer不使用 Newtonsoft json 但使用 System.Web.Script.Serialization.JavaScriptSerializer 反序列化具有不同数据类型的 json 字段
【发布时间】:2017-06-08 11:28:31
【问题描述】:

我在反序列化 json 数据时遇到问题,该数据可以具有浮点或数组类型的数据。同样的问题从这里 Dealing with JSON field that holds different types in C#

但在任何地方,解决方案都是使用JsonConverter。我需要在 c# 中仅使用 System.Web.Script.Serialization.JavaScriptSerializer 来实现反序列化。有人可以帮忙吗?

【问题讨论】:

  • 你的意思是json converter = Newtonsoft Json library 吗?
  • @RajshekarReddy 可能是指System.Web.Script.Serialization.JavaScriptSerializer
  • 是的,在这个问题的每个答案中都使用了 Newtonsoft json 库。由于一些限制,我不能这样做。我需要使用 System.Web.Script.Serialization.JavaScriptSerializer 来反序列化 json
  • javascript 按原样反序列化 json。不同类型的字段没有问题,因为 javascript 是动态类型语言。你到底有什么问题?
  • System.Web.Extensions.dll 中的“System.InvalidOperationException” 数组的反序列化不支持类型“System.Single”。这是我得到的例外。我有一个类,它的值是用 getter 和 setter 定义的。变量的数据类型当前设置为浮点数。当字段具有浮点值时,反序列化工作正常。但是当该字段具有数组值时,我会收到上述异常

标签: json.net c# json deserialization javascriptserializer


【解决方案1】:

您可以为此使用JavaScriptConverter。但是,与 Json.NET 的 JsonConverter 不同,JavaScriptConverter 只能用于映射到 JSON object 的类型——而不是数组或原始类型。因此,您需要为任何可能包含多态属性(可以是数组或单例项)的对象创建自定义转换器。

假设您有如下所示的 JSON:

{
  "name": "my name",
  "data": {
    "foo": "Foo",
    "bar": "Bar"
  },
  "values": [
    3.14,
    2.718
  ]
}

"values" 有时可能是这样的原始值:

  "values": 3.14

并且,您希望将其映射到以下 POCO:

public class RootObject
{
    public string name { get; set; }
    public NestedData data { get; set; }
    public float[] Values { get; set; }
}

public class NestedData
{
    public string foo { get; set; }
    public string bar { get; set; }
}

JavaScriptConverter.Deserialize() 被传递一个IDictionary<string, object> 的解析值时,要采取的步骤是:

  1. 分离任何需要自定义处理的属性(请记住,JavaScriptSerializer 不区分大小写,但字典不区分)。

  2. 使用JavaScriptSerializer.ConvertToType<T>() 使用不包含转换器的新序列化程序为任何剩余属性生成默认反序列化。

  3. 手动反序列化自定义属性并将其填充到部分反序列化的对象中,然后返回。

对于上面显示的类型,以下转换器在某种程度上基于this answer,可以完成这项工作:

class RootObjectConverter : CustomPropertiesConverter<RootObject>
{
    const string ValuesName = "values";

    protected override IEnumerable<string> CustomProperties
    {
        get { return new[] { ValuesName }; }
    }

    protected override void DeserializeCustomProperties(Dictionary<string, object> customDictionary, RootObject obj, JavaScriptSerializer serializer)
    {
        object itemCost;
        if (customDictionary.TryGetValue(ValuesName, out itemCost) && itemCost != null)
            obj.Values = serializer.FromSingleOrArray<float>(itemCost).ToArray();
    }

    protected override void SerializeCustomProperties(RootObject obj, Dictionary<string, object> dict, JavaScriptSerializer serializer)
    {
        obj.Values.ToSingleOrArray(dict, ValuesName);
    }
}

public abstract class CustomPropertiesConverter<T> : JavaScriptConverter
{
    protected abstract IEnumerable<string> CustomProperties { get; }

    protected abstract void DeserializeCustomProperties(Dictionary<string, object> customDictionary, T obj, JavaScriptSerializer serializer);

    protected abstract void SerializeCustomProperties(T obj, Dictionary<string, object> dict, JavaScriptSerializer serializer);

    public override object Deserialize(IDictionary<string, object> dictionary, Type type, JavaScriptSerializer serializer)
    {
        // Detach custom properties
        var customDictionary = new Dictionary<string, object>();
        foreach (var key in CustomProperties)
        {
            object value;
            if (dictionary.TryRemoveInvariant(key, out value))
                customDictionary.Add(key, value);
        }

        // Deserialize and populate all members other than "values"
        var obj = new JavaScriptSerializer().ConvertToType<T>(dictionary);

        // Populate custom properties
        DeserializeCustomProperties(customDictionary, obj, serializer);

        return obj;
    }

    public override IDictionary<string, object> Serialize(object obj, JavaScriptSerializer serializer)
    {
        // Generate a default serialization.  Is there an easier way to do this?
        var defaultSerializer = new JavaScriptSerializer();
        var dict = defaultSerializer.Deserialize<Dictionary<string, object>>(defaultSerializer.Serialize(obj));

        // Remove default serializations of custom properties, if present
        foreach (var key in CustomProperties)
        {
            dict.RemoveInvariant(key);
        }

        // Add custom properties
        SerializeCustomProperties((T)obj, dict, serializer);

        return dict;
    }

    public override IEnumerable<Type> SupportedTypes
    {
        get { return new[] { typeof(T) }; }
    }
}

public static class JavaScriptSerializerObjectExtensions
{
    public static void ReplaceInvariant<T>(this IDictionary<string, T> dictionary, string key, T value)
    {
        RemoveInvariant(dictionary, key);
        dictionary.Add(key, value);
    }

    public static bool TryRemoveInvariant<T>(this IDictionary<string, T> dictionary, string key, out T value)
    {
        if (dictionary == null)
            throw new ArgumentNullException();
        var keys = dictionary.Keys.Where(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)).ToArray();
        if (keys.Length == 0)
        {
            value = default(T);
            return false;
        }
        else if (keys.Length == 1)
        {
            value = dictionary[keys[0]];
            dictionary.Remove(keys[0]);
            return true;
        }
        else
        {
            throw new ArgumentException(string.Format("Duplicate keys found: {0}", String.Join(",", keys)));
        }
    }

    public static void RemoveInvariant<T>(this IDictionary<string, T> dictionary, string key)
    {
        if (dictionary == null)
            throw new ArgumentNullException();
        foreach (var actualKey in dictionary.Keys.Where(k => string.Equals(k, key, StringComparison.OrdinalIgnoreCase)).ToArray())
            dictionary.Remove(actualKey);
    }

    public static void ToSingleOrArray<T>(this ICollection<T> list, IDictionary<string, object> dictionary, string key)
    {
        if (dictionary == null)
            throw new ArgumentNullException();
        if (list == null || list.Count == 0)
            dictionary.RemoveInvariant(key);
        else if (list.Count == 1)
            dictionary.ReplaceInvariant(key, list.First());
        else
            dictionary.ReplaceInvariant(key, list.ToArray());
    }

    public static List<T> FromSingleOrArray<T>(this JavaScriptSerializer serializer, object value)
    {
        if (value == null)
            return null;
        if (value.IsJsonArray())
        {
            return value.AsJsonArray().Select(i => serializer.ConvertToType<T>(i)).ToList();
        }
        else
        {
            return new List<T> { serializer.ConvertToType<T>(value) };
        }
    }

    public static bool IsJsonArray(this object obj)
    {
        if (obj is string || obj is IDictionary)
            return false;
        return obj is IEnumerable;
    }

    public static IEnumerable<object> AsJsonArray(this object obj)
    {
        return (obj as IEnumerable).Cast<object>();
    }
}

然后像这样使用它:

var serializer = new JavaScriptSerializer();
serializer.RegisterConverters(new[] { new RootObjectConverter() });
var root = serializer.Deserialize<RootObject>(json);

【讨论】:

  • 嗨这个答案完美无缺,除了关于变量的错误。它适用于一维数组。当我有二维数组时如何处理?目前它在解析时抛出异常。
  • @Sanga - 它不是为处理这种情况而设计的;它旨在处理linked question 中的情况,其中属性值可以是单个项目或项目数组。如果这不是您的情况,您可能想通过包含您的实际 JSON 的 minimal reproducible example 提出另一个问题,这样社区就不会花时间解决您没有的问题。
  • 您好 dbc..感谢您的信息。我遇到了上述问题,您的回答对我有所帮助。给我的下一个 json 有多维数组。这就是为什么想知道是否可以对此代码进行最小的更改来处理多维数组。将为此发布一个新问题。祝你有美好的一天。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-21
  • 1970-01-01
  • 2017-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多