使用Json.net反序列化为任何未知类型适用于对象,但不适用于值类型

本文关键字:类型 适用于 对象 不适用 未知 net Json 反序列化 任何 使用 | 更新日期: 2023-09-27 18:28:47

我已经实现了一个基于Json.net的Json序列化程序,以接受任何对象类型并序列化它(用于放置到我的缓存中)

缓存接口不允许我指定类型,所以当我从缓存中检索时,我需要从元信息中动态创建类型。

它适用于对象,我现在面临的问题是,我不适用于值类型,我会得到一个异常,说一些类似cannot cast JValue to JObject的东西。

我的问题是,我如何满足值类型和对象类型的需求?如果有一个JObject的TryParse,那就太好了,我可以自己写,但感觉自己像掉进了兔子洞?

实现这一目标的最佳方式是什么?

我的代码如下,Json.net的设置:

_settings = new JsonSerializerSettings
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                NullValueHandling = NullValueHandling.Ignore,
                DateTimeZoneHandling = DateTimeZoneHandling.Utc,
                TypeNameHandling = TypeNameHandling.All
            };
_settings.Converters.Add(new StringEnumConverter());

设置函数(序列化):

public void Put(string cacheKey, object toBeCached, TimeSpan cacheDuration)
        {
            _cache.Set(cacheKey, JsonConvert.SerializeObject(toBeCached, _settings), cacheDuration);
        }

和get(反序列化):

 public object Get(string cacheKey)
    {
        try
        {
            var value = _cache.Get(cacheKey);
            if (!value.HasValue)
            {
                return null;
            }
            var jobject = JsonConvert.DeserializeObject<JObject>(value);
            var typeName = jobject?["$type"].ToString();
            if (typeName == null)
            {
                return null;
            }
            var type = Type.GetType(typeName);
            return jobject.ToObject(type);
        }
        catch (Exception e)
        {
            // Todo
            return null;
        }
    }

使用Json.net反序列化为任何未知类型适用于对象,但不适用于值类型

您需要解析为JToken而不是JObject,然后检查返回的类型是否是包含JSON原语的JValue

public static object Get(string value)
{
    var jToken = JsonConvert.DeserializeObject<JToken>(value);
    if (jToken == null)
        return null;
    else if (jToken is JValue)
    {
        return ((JValue)jToken).Value;
    }
    else
    {
        if (jToken["$type"] == null)
            return null;
        // Use the same serializer settings as used during serialization.
        // Ideally with a proper SerializationBinder that sanitizes incoming types as suggested
        // in https://www.newtonsoft.com/json/help/html/T_Newtonsoft_Json_TypeNameHandling.htm
        var _settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            NullValueHandling = NullValueHandling.Ignore,
            DateTimeZoneHandling = DateTimeZoneHandling.Utc,
            TypeNameHandling = TypeNameHandling.All,
            Converters = { new StringEnumConverter() },
            //SerializationBinder = new SafeSerializationBinder(),
        };
        // Since the JSON contains a $type parameter and TypeNameHandling is enabled, if we deserialize 
        // to type object the $type information will be used to determine the actual type, using Json.NET's
        // serialization binder: https://www.newtonsoft.com/json/help/html/SerializeSerializationBinder.htm
        return jToken.ToObject(typeof(object), JsonSerializer.CreateDefault(_settings));
    }
}

然而,请注意,基元的类型信息不会精确地往返:

  • 整数将作为long返回(如果大于long.MaxValue,则返回BigInteger)。

  • 浮点值将作为doubledecimal返回,具体取决于JsonSerializerSettings.FloatParseHandling的设置——FloatParseHandling.DoubleFloatParseHandling.Decimal

  • "看起来像"日期的JSON字符串将被转换为DateTimeDateTimeOffset或保留为字符串,具体取决于设置JsonSerializerSettings.DateParseHandling

  • enum名称将转换为字符串。

如果您需要往返于基元的类型信息,可以考虑使用TypeWrapper<T>将特定的枚举反序列化到Json.Net中的system.enum中来封装您的根对象。

最后,如果您可能正在反序列化不受信任的JSON(如果您正在从文件或互联网反序列化,那么您肯定是反序列化的),请注意JSON.NET文档中的以下警告:

当应用程序从外部源反序列化JSON时,应谨慎使用TypeNameHandling使用None以外的值进行反序列化时,应使用自定义SerializationBinder验证传入类型

关于为什么这可能是必要的讨论,请参阅Newtonsoft Json中的TypeNameHandling警告,如何配置Json.NET以创建易受攻击的web API,以及Alvaro Muñoz&Oleksandr Mirosh的黑帽子纸https://www.blackhat.com/docs/us-17/thursday/us-17-Munoz-Friday-The-13th-JSON-Attacks-wp.pdf