起初我误解了这个问题。当我使用 Xml 时,我认为这很容易。只需向属性添加一个属性并将属性留空。但正如我发现的那样,Json 不是那样工作的。由于我一直在寻找适用于 xml 和 json 的解决方案,因此您将在此答案中找到 xml 引用。另一件事,我写这个时考虑到了一个 C# 客户端。
第一步是创建两个用于序列化的类。
public class ChangeType
{
[JsonProperty("#text")]
[XmlText]
public string Text { get; set; }
}
public class GenericChangeType<T> : ChangeType
{
}
我选择了泛型和非泛型类,因为很难转换为泛型类型,而这并不重要。此外,对于 xml 实现,XmlText 必须是字符串。
XmlText 是属性的实际值。优点是您可以向此对象添加属性,并且这是一个对象,而不仅仅是字符串。在 XML 中它看起来像:<Firstname>John</Firstname>
对于 Json,这不起作用。 Json 不知道属性。所以对于 Json 这只是一个具有属性的类。为了实现 xml 值的想法(稍后我会谈到),我将属性重命名为 #text。这只是一个约定。
由于 XmlText 是字符串(我们想序列化为字符串),因此可以存储值而忽略类型。但是在序列化的情况下,我想知道实际的类型。
缺点是viewmodel需要引用这些类型,优点是属性为序列化强类型:
public class CustomerViewModel
{
public GenericChangeType<int> Id { get; set; }
public ChangeType Firstname { get; set; }
public ChangeType Lastname { get; set; }
public ChangeType Reference { get; set; }
}
假设我设置了值:
var customerViewModel = new CustomerViewModel
{
// Where int needs to be saved as string.
Id = new GenericeChangeType<int> { Text = "12" },
Firstname = new ChangeType { Text = "John" },
Lastname = new ChangeType { },
Reference = null // May also be omitted.
}
在 xml 中,这看起来像:
<CustomerViewModel>
<Id>12</Id>
<Firstname>John</Firstname>
<Lastname />
</CustomerViewModel>
这足以让服务器检测到更改。但是使用 json 会生成以下内容:
{
"id": { "#text": "12" },
"firstname": { "#text": "John" },
"lastname": { "#text": null }
}
它可以工作,因为在我的实现中,接收视图模型具有相同的定义。但是由于您只是在谈论序列化,并且如果您使用其他实现,您会想要:
{
"id": 12,
"firstname": "John",
"lastname": null
}
这就是我们需要添加一个自定义 json 转换器来产生这个结果的地方。相关代码在 WriteJson 中,假设您只将此转换器添加到序列化程序设置中。但为了完整起见,我也添加了 readJson 代码。
public class ChangeTypeConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
// This is important, we can use this converter for ChangeType only
return typeof(ChangeType).IsAssignableFrom(objectType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = JToken.Load(reader);
// Types match, it can be deserialized without problems.
if (value.Type == JTokenType.Object)
return JsonConvert.DeserializeObject(value.ToString(), objectType);
// Convert to ChangeType and set the value, if not null:
var t = (ChangeType)Activator.CreateInstance(objectType);
if (value.Type != JTokenType.Null)
t.Text = value.ToString();
return t;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var d = value.GetType();
if (typeof(ChangeType).IsAssignableFrom(d))
{
var changeObject = (ChangeType)value;
// e.g. GenericChangeType<int>
if (value.GetType().IsGenericType)
{
try
{
// type - int
var type = value.GetType().GetGenericArguments()[0];
var c = Convert.ChangeType(changeObject.Text, type);
// write the int value
writer.WriteValue(c);
}
catch
{
// Ignore the exception, just write null.
writer.WriteNull();
}
}
else
{
// ChangeType object. Write the inner string (like xmlText value)
writer.WriteValue(changeObject.Text);
}
// Done writing.
return;
}
// Another object that is derived from ChangeType.
// Do not add the current converter here because this will result in a loop.
var s = new JsonSerializer
{
NullValueHandling = serializer.NullValueHandling,
DefaultValueHandling = serializer.DefaultValueHandling,
ContractResolver = serializer.ContractResolver
};
JToken.FromObject(value, s).WriteTo(writer);
}
}
起初我尝试将转换器添加到类:[JsonConverter(ChangeTypeConverter)]。但问题是转换器将始终被使用,这会创建一个引用循环(如上面代码中的注释中所述)。此外,您可能只想将此转换器用于序列化。这就是为什么我只将它添加到序列化程序中:
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new ChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var s = JsonConvert.SerializeObject(customerViewModel, serializerSettings);
这将生成我正在寻找的 json,并且应该足以让服务器检测到更改。
-- 更新--
由于此答案侧重于序列化,因此最重要的是 lastname 是序列化字符串的一部分。然后取决于接收方如何再次将字符串反序列化为对象。
序列化和反序列化使用不同的设置。为了再次反序列化,您可以使用:
var deserializerSettings = new JsonSerializerSettings
{
//NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate,
Converters = new List<JsonConverter> { new Converters.NoChangeTypeConverter() },
ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver()
};
var obj = JsonConvert.DeserializeObject<CustomerViewModel>(s, deserializerSettings);
如果你使用相同的类进行反序列化,那么 Request.Lastname 应该是 ChangeType,Text = null。
我不确定为什么从反序列化设置中删除 NullValueHandling 会导致您的情况出现问题。但是您可以通过将空对象写入值而不是 null 来克服这个问题。在转换器中,当前的 ReadJson 已经可以处理这个问题。但是在 WriteJson 中必须进行修改。而不是writer.WriteValue(changeObject.Text);,您需要类似:
if (changeObject.Text == null)
JToken.FromObject(new ChangeType(), s).WriteTo(writer);
else
writer.WriteValue(changeObject.Text);
这将导致:
{
"id": 12,
"firstname": "John",
"lastname": {}
}