【发布时间】:2023-04-08 04:55:02
【问题描述】:
我需要能够区分未提供的键和空键。
JSON 的一个例子是:
# key not specified
{}
# key specified but null
{'optionalKey' : null}
# key specified and is valid
{'optionalKey' : 123}
为了区分键的缺失和空值,我创建了一个通用的 Optional 类来包装每个字段,但这需要编写自定义 JsonConverter 和 DefaultContractResolver 来展平 JSON/解压缩 OptionalType(为每个字段发送嵌套的 JSON 是不是一个选项)。
我已经设法创建了一个 LINQPad 脚本来执行此操作,但我不禁想到必须有一种不涉及反射的更简单的方法?
void Main()
{
//null
Settings settings = null;
JsonConvert.SerializeObject(settings, new JsonSerializerSettings() { ContractResolver = new ShouldSerializeContractResolver() }).Dump();
settings = new Settings();
// no key {}
settings.OptionalIntegerSetting = null;
JsonConvert.SerializeObject(settings, new JsonSerializerSettings() { ContractResolver = new ShouldSerializeContractResolver() }).Dump();
// null key {\"OptionalIntegerSetting\" : null}
settings.OptionalIntegerSetting = new Optional<uint?>(); // assigning this to null assigns the optional type class, it does not use the implict operators.
JsonConvert.SerializeObject(settings, new JsonSerializerSettings() { ContractResolver = new ShouldSerializeContractResolver() }).Dump();
// has value {\"OptionalIntegerSetting\" : 123}
settings.OptionalIntegerSetting = 123;
JsonConvert.SerializeObject(settings, new JsonSerializerSettings() { ContractResolver = new ShouldSerializeContractResolver() }).Dump();
JsonConvert.DeserializeObject<Settings>("{}").Dump();
JsonConvert.DeserializeObject<Settings>("{'OptionalIntegerSetting' : null}").Dump();
JsonConvert.DeserializeObject<Settings>("{'OptionalIntegerSetting' : '123'}").Dump(); // supplying 'a string' instead of '123' currently breaks OptionalConverter.ReadJson
}
public class Settings
{
public Optional<uint?> OptionalIntegerSetting { get; set; }
}
[JsonConverter(typeof(OptionalConverter))]
public class Optional<T>
{
public T Value { get; set; }
public Optional() { }
public Optional(T value)
{
Value = value;
}
public static implicit operator Optional<T>(T t)
{
return new Optional<T>(t);
}
public static implicit operator T(Optional<T> t)
{
return t.Value;
}
}
// Provides a way of populating the POCO Resource model with CanSerialise proerties at the point just before serialisation.
// This prevents having to define a CanSerialiseMyProperty method for each property.
public class ShouldSerializeContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
JsonProperty property = base.CreateProperty(member, memberSerialization);
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Optional<>))
{
// add an additional ShouldSerialize property to omit no json
property.ShouldSerialize = instance =>
instance.GetType().GetProperty(property.PropertyName).GetValue(instance) != null;
}
return property;
}
}
// Performs the conversion to and from a JSON value to compound type
public class OptionalConverter : JsonConverter
{
public override bool CanWrite => true;
public override bool CanRead => true;
public override bool CanConvert(Type objectType)
{
return objectType.IsGenericType && objectType.GetGenericTypeDefinition() == typeof(Optional<>);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var jtoken = JToken.Load(reader);
var genericTypeArgument = objectType.GetGenericArguments()[0];
var constructor = objectType.GetConstructor(new[] { genericTypeArgument });
var result = JTokenType.Null != jtoken.Type ? jtoken.ToObject(genericTypeArgument) : null;
return constructor.Invoke(new object[] { JTokenType.Null != jtoken.Type ? jtoken.ToObject(genericTypeArgument) : null });
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var val = value.GetType().GetProperty("Value").GetValue(value);
(val != null ? JValue.FromObject(val) : JValue.CreateNull()).WriteTo(writer);
}
}
【问题讨论】:
-
如this answer 和this one 中所述,Json.NET 支持XXXSpecified pattern。也许这可以满足您的需求?
-
@dbc 这是一种更简单的序列化方法,但我会失去在反序列化过程中区分空键和空键的能力。
-
当且仅当遇到名称为
xxx的属性时才应设置xxxSpecified属性 - 即使是空值。这不是你想要的吗? -
那就完美了,谢谢!我发现使用 [JsonIgnore] 属性比实现自定义 ContractResolver 来省略 xxxSpecified 属性更简单。
-
这是另一种您可能会感兴趣的方法:stackoverflow.com/questions/44928074/…
标签: c# .net json asp.net-web-api json.net