问题
I'm struggling with custom json serialization in .Net core, I'm trying to make all properties required by default except if property has specific type. Here is an example of what I'm trying to achieve:
Let's assument that I have following type: F#:
type FooType = {
id: int
name: string
optional: int option
}
you can think about below code as similar to following in C#:
class FooType =
{
int Id {get;set;};
string Name {get;set;};
Nullable<int> Optional {get;set;};
}
What I'm trying to do is to return error if Id or Name property is missing in json object but deserialize without error if Optional is missing (so basically to set Property as required or not based on it's type). I'm able to mark all properties as required by using RequireObjectPropertiesContractResolver
from this sample: https://stackoverflow.com/a/29660550 but unfortunately I wasn't able to build something more dynamic.
I have also default converter for Optional types I would like to add to serialization. It's not part of this one specific question but if you have an idea how to mark property to be required or not and to use custom Converter in one place than it would be even greater.
回答1:
You can combine the contract resolver from Json.NET require all properties on deserialization with the logic from the answer to Reflection to find out if property is of option type by p.s.w.g to mark all members except those that are optional as required:
type RequireObjectPropertiesContractResolver() =
inherit DefaultContractResolver()
override __.CreateObjectContract(objectType: Type) =
let contract = base.CreateObjectContract objectType
contract.ItemRequired <- System.Nullable<Required>(Required.Always)
contract
override __.CreateProperty(memberInfo: MemberInfo, memberSerialization: MemberSerialization) =
let property = base.CreateProperty(memberInfo, memberSerialization)
// https://stackoverflow.com/questions/20696262/reflection-to-find-out-if-property-is-of-option-type
let isOption = property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>>
if isOption then (
property.Required <- Required.Default
property.NullValueHandling <- new System.Nullable<NullValueHandling>(NullValueHandling.Ignore)
)
property
Then, deserialize as follows:
let settings = new JsonSerializerSettings(ContractResolver = RequireObjectPropertiesContractResolver())
let obj = JsonConvert.DeserializeObject<FooType>(inputJson, settings)
Notes:
I also added
NullValueHandling.Ignore
so that optional members with no value would not get serialized.You may want to cache the contract resolver for best performance.
Option<'T>
is not the same asNullable<'T>
. I checked fortypedefof<Option<_>>
but you could add a check fortypedefof<System.Nullable<_>>
as well if you want:let isOption = property.PropertyType.IsGenericType && (property.PropertyType.GetGenericTypeDefinition() = typedefof<Option<_>> || property.PropertyType.GetGenericTypeDefinition() = typedefof<System.Nullable<_>>)
Sample fiddle, which demonstrates that the string {"id":101,"name":"John"}
can be deserialized, but the string {"id":101}
cannot.
来源:https://stackoverflow.com/questions/48327799/json-net-make-property-required-based-on-property-type