2017-07-28 39 views
0

我需要能夠區分未提供的鍵和null。實現ASP.NET Web API可選參數

的JSON的一個例子是:

# key not specified 
{} 

# key specified but null 
{'optionalKey' : null} 

# key specified and is valid 
{'optionalKey' : 123} 

要在關鍵的缺席與空之間區分的,我已經創建了包裝的每個字段的一般可選類,但這需要編寫自定義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); 
    } 
} 
+0

如上述[此答案](https://stackoverflow.com/a/39224495/3744182)和[此酮](https://stackoverflow.com/a/32407208/3744182),JSON .NET支持[XXXSpecified模式](https://msdn.microsoft.com/en-us/library/zds0b35c.aspx)。也許這符合你的需求? – dbc

+0

@dbc這對序列化來說簡單得多,但是我沒有能力在反序列化過程中區分空鍵和空。 – Andrew

+1

當且僅當遇到名稱爲「xxx」的屬性時,纔會設置'xxxSpecified'屬性 - 即使使用空值。這不是你想要的嗎? – dbc

回答

2

完整功勞歸於@dbc。

void Main() 
{ 
    var settings = new Settings(); 

    // no key {} 
    settings.OptionalIntegerSetting = null; 
    JsonConvert.SerializeObject(settings).Dump(); 

    // null key {\"OptionalIntegerSetting\" : null} 
    settings.OptionalIntegerSetting = null; 
    settings.OptionalIntegerSettingSpecified = true; 
    JsonConvert.SerializeObject(settings).Dump(); 

    // has value {\"OptionalIntegerSetting\" : 123} 
    settings.OptionalIntegerSetting = 123; 
    JsonConvert.SerializeObject(settings).Dump(); 

    JsonConvert.DeserializeObject<Settings>("{}").Dump(); 
    JsonConvert.DeserializeObject<Settings>("{'OptionalIntegerSetting' : null}").Dump(); 
    JsonConvert.DeserializeObject<Settings>("{'OptionalIntegerSetting' : '123'}").Dump(); 
} 

public class Settings 
{ 
    public uint? OptionalIntegerSetting { get; set; } 

    [JsonIgnore] 
    public bool OptionalIntegerSettingSpecified { get; set;} 
}