使用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;
}
}
您需要解析为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
)。浮点值将作为
double
或decimal
返回,具体取决于JsonSerializerSettings.FloatParseHandling
的设置——FloatParseHandling.Double
或FloatParseHandling.Decimal
。"看起来像"日期的JSON字符串将被转换为
DateTime
、DateTimeOffset
或保留为字符串,具体取决于设置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