Newtonsoft Json.NET JsonConverter 属性在反序列化时保留引用问题
Newtonsoft Json.NET JsonConverter attribute preserve references issue when deserializing
在一个项目的模型中,我使用 JsonConverter
属性来帮助这些模型的(反)序列化。
转换器目前看起来像这样:
public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
bool _canWrite = true;
public override bool CanWrite
{
get { return _canWrite; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
serializer.DefaultValueHandling = DefaultValueHandling.Ignore;
serializer.NullValueHandling = NullValueHandling.Ignore;
_canWrite = false;
var jObject = JObject.FromObject(value, serializer);
_canWrite = true;
jObject.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
if (reader.TokenType == JsonToken.StartObject)
{
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
throw new JsonSerializationException();
}
}
public override bool CanConvert(Type objectType)
{
return typeof(IModelBase).IsAssignableFrom(objectType);
}
}
模型的底座 class 如下所示:
[JsonConverter(typeof(CustomJsonConverter))]
public abstract class ModelBase : IModelBase
{
public string ID { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
ModelBase
class 的派生 class 具有类型也派生自 ModelBase
的属性。例如:
public class CustomerModel : ModelBase
{
public string Name { get; set; }
public UserModel CreatedBy { get; set; }
public UserModel ModifiedBy { get; set; }
}
public class UserModel : ModelBase
{
public string Name { get; set; }
public UserModel CreatedBy { get; set; }
public UserModel ModifiedBy { get; set; }
}
我在 ASP.NET Web API 2 应用程序(服务器端)和 C# 应用程序(客户端)中使用这些模型。对于大多数 API 调用,都会返回一个模型数组。序列化模型时,一切都按预期进行。但是,在反序列化时,只有第一次出现的每个引用被填充信息。
例如:
[
{
"$id": "1",
"Name": "Customer1",
"CreatedBy": {
"$id": "2",
"ID": "1",
"Name": "User1"
},
"ModifiedBy": {
"$id": "3",
"ID": "3",
"Name": "User3"
},
"ID": "1",
"CreatedAt": "2019-02-06T00:00:04",
"ModifiedAt": "2019-02-06T00:20:12"
},
{
"$id": "4",
"Name": "Customer2",
"CreatedBy": {
"$ref": "2"
},
"ModifiedBy": {
"$ref": "2"
},
"ID": "2",
"CreatedAt": "2019-02-06T00:10:00",
"ModifiedAt": "2019-02-06T00:10:00"
}
]
当尝试反序列化 Web API 返回的这个 JSON 对象时,第一个 CustomerModel
的 CreatedBy
和 ModifiedBy
属性将是正确的目的。但是,对于第二个 CustomerModel
对象,这些属性将是没有任何属性设置的新 UserModel
实例。
要反序列化 JSON 字符串,我使用以下代码:
using (var sr = new StreamReader(streamFromWebAPICall))
{
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
return js.Deserialize(jtr, objectType);
}
}
如何正确设置所有反序列化对象的属性?
编辑:
问题似乎出在 serializer.Populate(reader, existingValue)
,其中引用未被记住。
您的基本问题是您提供的是 custom JsonConverter
for a type for which you also want to enable PreserveReferencesHandling
。但是无论何时应用自定义转换器,它都必须手动处理一切,包括解析和生成"$ref"
和"$id"
属性。您的转换器不会这样做,因此您的反序列化代码不会正确反序列化您的对象图。
to 包括一个模板转换器,显示如何处理这些属性。但是,由于您的转换器似乎只是在(反)序列化之前切换一些序列化程序设置,您可以简单地调用递归(反)序列化对象,在持续时间内禁用转换器,如下所示:
public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
serializer.Serialize(writer, value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
return serializer.Deserialize(reader, objectType);
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
}
请注意,禁用转换器的逻辑需要线程安全,因为 Json.NET 将跨线程共享契约和转换器。
演示 fiddle #1 here.
作为替代方案,您可以完全取消转换器并将 [JsonObject(IsReference = true)]
直接应用于 ModelBase
:
[JsonObject(IsReference = true)]
public abstract class ModelBase : IModelBase
{
public string ID { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
然后使用在设置中指定的 DefaultValueHandling.Ignore
和 NullValueHandling.Ignore
进行序列化和反序列化,如下所示:
static object Deserialize(Stream streamFromWebAPICall, Type objectType)
{
using (var sr = new StreamReader(streamFromWebAPICall))
{
using (var jtr = new JsonTextReader(sr))
{
var settings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
var js = JsonSerializer.CreateDefault(settings);
return js.Deserialize(jtr, objectType);
}
}
}
演示 fiddle #2 here.
请注意,您也可以设置 [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
as of Json.NET 11.0.1,但在 JsonObjectAttribute
上似乎没有 ItemDefaultValueHandling
设置,因此将其添加到序列化程序设置中(或使用可空值作为可选值-type 值,如 CreatedAt
) 是必需的。
在一个项目的模型中,我使用 JsonConverter
属性来帮助这些模型的(反)序列化。
转换器目前看起来像这样:
public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
bool _canWrite = true;
public override bool CanWrite
{
get { return _canWrite; }
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
serializer.DefaultValueHandling = DefaultValueHandling.Ignore;
serializer.NullValueHandling = NullValueHandling.Ignore;
_canWrite = false;
var jObject = JObject.FromObject(value, serializer);
_canWrite = true;
jObject.WriteTo(writer);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
serializer.PreserveReferencesHandling = PreserveReferencesHandling.Objects;
if (reader.TokenType == JsonToken.StartObject)
{
existingValue = existingValue ?? serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
serializer.Populate(reader, existingValue);
return existingValue;
}
else if (reader.TokenType == JsonToken.Null)
{
return null;
}
else
{
throw new JsonSerializationException();
}
}
public override bool CanConvert(Type objectType)
{
return typeof(IModelBase).IsAssignableFrom(objectType);
}
}
模型的底座 class 如下所示:
[JsonConverter(typeof(CustomJsonConverter))]
public abstract class ModelBase : IModelBase
{
public string ID { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
ModelBase
class 的派生 class 具有类型也派生自 ModelBase
的属性。例如:
public class CustomerModel : ModelBase
{
public string Name { get; set; }
public UserModel CreatedBy { get; set; }
public UserModel ModifiedBy { get; set; }
}
public class UserModel : ModelBase
{
public string Name { get; set; }
public UserModel CreatedBy { get; set; }
public UserModel ModifiedBy { get; set; }
}
我在 ASP.NET Web API 2 应用程序(服务器端)和 C# 应用程序(客户端)中使用这些模型。对于大多数 API 调用,都会返回一个模型数组。序列化模型时,一切都按预期进行。但是,在反序列化时,只有第一次出现的每个引用被填充信息。
例如:
[
{
"$id": "1",
"Name": "Customer1",
"CreatedBy": {
"$id": "2",
"ID": "1",
"Name": "User1"
},
"ModifiedBy": {
"$id": "3",
"ID": "3",
"Name": "User3"
},
"ID": "1",
"CreatedAt": "2019-02-06T00:00:04",
"ModifiedAt": "2019-02-06T00:20:12"
},
{
"$id": "4",
"Name": "Customer2",
"CreatedBy": {
"$ref": "2"
},
"ModifiedBy": {
"$ref": "2"
},
"ID": "2",
"CreatedAt": "2019-02-06T00:10:00",
"ModifiedAt": "2019-02-06T00:10:00"
}
]
当尝试反序列化 Web API 返回的这个 JSON 对象时,第一个 CustomerModel
的 CreatedBy
和 ModifiedBy
属性将是正确的目的。但是,对于第二个 CustomerModel
对象,这些属性将是没有任何属性设置的新 UserModel
实例。
要反序列化 JSON 字符串,我使用以下代码:
using (var sr = new StreamReader(streamFromWebAPICall))
{
using (var jtr = new JsonTextReader(sr))
{
var js = new JsonSerializer();
return js.Deserialize(jtr, objectType);
}
}
如何正确设置所有反序列化对象的属性?
编辑:
问题似乎出在 serializer.Populate(reader, existingValue)
,其中引用未被记住。
您的基本问题是您提供的是 custom JsonConverter
for a type for which you also want to enable PreserveReferencesHandling
。但是无论何时应用自定义转换器,它都必须手动处理一切,包括解析和生成"$ref"
和"$id"
属性。您的转换器不会这样做,因此您的反序列化代码不会正确反序列化您的对象图。
public class CustomJsonConverter : Newtonsoft.Json.JsonConverter
{
[ThreadStatic]
static bool disabled;
// Disables the converter in a thread-safe manner.
bool Disabled { get { return disabled; } set { disabled = value; } }
public override bool CanWrite { get { return !Disabled; } }
public override bool CanRead { get { return !Disabled; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
serializer.Serialize(writer, value);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
{
return null;
}
using (new PushValue<bool>(true, () => Disabled, val => Disabled = val))
using (new PushValue<PreserveReferencesHandling>(PreserveReferencesHandling.Objects, () => serializer.PreserveReferencesHandling, val => serializer.PreserveReferencesHandling = val))
using (new PushValue<DefaultValueHandling>(DefaultValueHandling.Ignore, () => serializer.DefaultValueHandling, val => serializer.DefaultValueHandling = val))
using (new PushValue<NullValueHandling>(NullValueHandling.Ignore, () => serializer.NullValueHandling, val => serializer.NullValueHandling = val))
{
return serializer.Deserialize(reader, objectType);
}
}
public override bool CanConvert(Type objectType)
{
throw new NotImplementedException();
}
}
public struct PushValue<T> : IDisposable
{
Action<T> setValue;
T oldValue;
public PushValue(T value, Func<T> getValue, Action<T> setValue)
{
if (getValue == null || setValue == null)
throw new ArgumentNullException();
this.setValue = setValue;
this.oldValue = getValue();
setValue(value);
}
// By using a disposable struct we avoid the overhead of allocating and freeing an instance of a finalizable class.
public void Dispose()
{
if (setValue != null)
setValue(oldValue);
}
}
请注意,禁用转换器的逻辑需要线程安全,因为 Json.NET 将跨线程共享契约和转换器。
演示 fiddle #1 here.
作为替代方案,您可以完全取消转换器并将 [JsonObject(IsReference = true)]
直接应用于 ModelBase
:
[JsonObject(IsReference = true)]
public abstract class ModelBase : IModelBase
{
public string ID { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime ModifiedAt { get; set; }
}
然后使用在设置中指定的 DefaultValueHandling.Ignore
和 NullValueHandling.Ignore
进行序列化和反序列化,如下所示:
static object Deserialize(Stream streamFromWebAPICall, Type objectType)
{
using (var sr = new StreamReader(streamFromWebAPICall))
{
using (var jtr = new JsonTextReader(sr))
{
var settings = new JsonSerializerSettings
{
DefaultValueHandling = DefaultValueHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore,
};
var js = JsonSerializer.CreateDefault(settings);
return js.Deserialize(jtr, objectType);
}
}
}
演示 fiddle #2 here.
请注意,您也可以设置 [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
as of Json.NET 11.0.1,但在 JsonObjectAttribute
上似乎没有 ItemDefaultValueHandling
设置,因此将其添加到序列化程序设置中(或使用可空值作为可选值-type 值,如 CreatedAt
) 是必需的。