按照官方文档:
string jsonTypeNameAll = JsonConvert.SerializeObject(stockholder, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
Console.WriteLine(jsonTypeNameAll);
// {
// "$type": "Newtonsoft.Json.Samples.Stockholder, Newtonsoft.Json.Tests",
// "FullName": "Steve Stockholder",
// "Businesses": {
// "$type": "System.Collections.Generic.List`1[[Newtonsoft.Json.Samples.Business, Newtonsoft.Json.Tests]], mscorlib",
// "$values": [
// {
// "$type": "Newtonsoft.Json.Samples.Hotel, Newtonsoft.Json.Tests",
// "Stars": 4,
// "Name": "Hudson Hotel"
// }
// ]
// }
// }
Run Code Online (Sandbox Code Playgroud)
我已经复制粘贴了这段代码。
public static string Convert<T>(T cacheObject)
{
return JsonConvert.SerializeObject(cacheObject, Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
}
Run Code Online (Sandbox Code Playgroud)
但是,如果我用 调用它Convert(DateTime.Now),我会得到一个序列化的 DateTime 字符串,不包含类型:
我究竟做错了什么?
TypeNameHandling"$type"其工作原理是向 JSON对象添加一个特殊的保留属性,指定序列化的 .Net 类型。此外,正如 Newtonsoft 的序列化指南中所解释的,数组将嵌套在指定类型的包装对象内:
请注意,如果在序列化器上为 JSON 数组启用了 TypeNameHandling 或 PreserveReferencesHandling,则 JSON 数组将包装在包含对象中。该对象将具有类型名称/引用属性和 $values 属性,该属性将包含集合数据。
然而,序列化 JSON 原语时并没有实现这种特殊情况。序列化为原语的 .Net 对象将作为原语发出,而不添加任何包装器,即使TypeNameHandling指定了也是如此。
因此,如果您想为 JSON 原语指定 .Net 类型信息,则必须自己创建一个包装器对象。一些示例可以在Deserialize Dictionary<string, object> with enum value in C# 的答案或JSON.net (de)serialize untyped property的答案中找到。
根据这些答案,创建以下包装器:
public abstract class TypeWrapper
{
// Taken from this answer /sf/answers/2683826281/
// To /sf/ask/2683547331/
// By /sf/users/262092771/
protected TypeWrapper() { }
[JsonIgnore]
public abstract object ObjectValue { get; }
public static TypeWrapper CreateWrapper<T>(T value)
{
if (value == null)
return new TypeWrapper<T>();
var type = value.GetType();
if (type == typeof(T))
return new TypeWrapper<T>(value);
// Return actual type of subclass
return (TypeWrapper)Activator.CreateInstance(typeof(TypeWrapper<>).MakeGenericType(type), value);
}
}
public sealed class TypeWrapper<T> : TypeWrapper
{
public TypeWrapper() : base() { }
public TypeWrapper(T value)
: base()
{
this.Value = value;
}
public override object ObjectValue { get { return Value; } }
public T Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
然后修改你的Convert<T>()如下:
public static partial class JsonExtensions
{
static readonly IContractResolver globalResolver = new JsonSerializer().ContractResolver;
public static string Convert<T>(T cacheObject)
{
return JsonConvert.SerializeObject(ToTypeWrapperIfRequired(cacheObject), Formatting.Indented, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
}
public static T UnConvert<T>(string json)
{
var obj = JsonConvert.DeserializeObject<object>(json, new JsonSerializerSettings
{
TypeNameHandling = TypeNameHandling.All
});
if ((obj is TypeWrapper wrapper))
return (T)wrapper.ObjectValue;
return (T)obj;
}
static object ToTypeWrapperIfRequired<T>(T obj, IContractResolver resolver = null)
{
resolver = resolver ?? globalResolver;
if (obj == null)
return null;
// Type information is redundant for string or bool
if (obj is bool || obj is string)
return obj;
var contract = resolver.ResolveContract(obj.GetType());
if (contract is JsonPrimitiveContract)
return TypeWrapper.CreateWrapper(obj);
return obj;
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以转换为 JSON 并返回,如下所示:
var json = JsonExtensions.Convert(obj);
var objBack = JsonExtensions.UnConvert<T>(json);
Run Code Online (Sandbox Code Playgroud)
笔记:
仅在必要时才会将包装器添加到正在序列化的对象中 - 即,如果 .Net 对象将被序列化为 JSON 原语而不是 JSON 对象或数组。此外,如果传入对象是 astring或 a,我不会添加包装器bool,因为这些类型可以从 JSON 中明确推断出来。
去做:
JValue.Convert<T>()序列化和反序列化已包装的对象会导致其在反序列化时解开包装,因此如果传入对象是包装器,您可能需要抛出异常。请注意,使用TypeNameHandling可能会给您的应用程序带来安全漏洞。有关详细信息,请参阅Newtonsoft Json 中的 TypeNameHandling 警告和由于 Json.Net TypeNameHandling auto 而导致外部 json 易受攻击?。
另请注意,类型名称信息并不总是可以在不同的 .Net 框架之间移植。例如,请参见不同框架中的序列化和反序列化#1378。在这种情况下您可能需要编写自定义ISerializationBinder。
演示小提琴在这里。
| 归档时间: |
|
| 查看次数: |
1793 次 |
| 最近记录: |