Comment lever une exception lorsque le nom du paramètre JsonConstructor ne correspond pas à JSON?
Je désérialise un tas de structures en lecture seule C # (dont les constructeurs sont marqués par [JsonConstructor]), et j'essaie d'échouer tôt si un JSON que je reçois est malformé.
Malheureusement, s'il existe une différence de dénomination entre le paramètre du constructeur et le JSON d'entrée, le paramètre se voit simplement attribuer une valeur par défaut. Existe-t-il un moyen d'obtenir une exception à la place, afin que ces valeurs par défaut ne «polluent» pas accidentellement le reste de ma logique métier? J'ai essayé de jouer avec divers JsonSerializerSettingsmais en vain.
Exemple simplifié:
public readonly struct Foo {
[JsonConstructor]
public Foo(long wrong) {
FooField = wrong;
}
public readonly long FooField;
}
public void JsonConstructorParameterTest() {
// The Foo constructor parameter name ("wrong") doesn't match the JSON property name ("FooField").
var foo = JsonConvert.DeserializeObject<Foo>("{\"FooField\":42}");
// The foo.FooField is now 0.
// How can we cause the above to throw an exception instead of just assigning 0 to Foo.FooField?
}
Ce qui précède peut être corrigé en renommant wrongen fooField, mais j'aimerais savoir qu'avant 0 a déjà été validé dans ma base de données.
Réponses
Le résolveur de contrat de cette réponse à JSON.net ne doit pas utiliser les valeurs par défaut pour les paramètres du constructeur, doit utiliser default pour les propriétés fait presque ce que vous voulez, mais il a une restriction notée:
Cela ne fonctionne que s'il existe une propriété correspondante. Il ne semble pas y avoir de moyen simple de marquer un paramètre de constructeur sans propriété correspondante comme requis.
Puisque marquer un paramètre de constructeur "sans correspondance" comme requis ne semble pas fonctionner (le violon de démonstration n ° 1 ici ), vous pouvez modifier le résolveur de contrat à partir de cette réponse pour lancer une exception lors de la construction du contrat si un paramètre de constructeur sans correspondance est trouvé.
Le résolveur de contrat suivant fait cela:
public class ConstructorParametersRequiredContractResolver : DefaultContractResolver
{
protected override JsonProperty CreatePropertyFromConstructorParameter(JsonProperty matchingMemberProperty, ParameterInfo parameterInfo)
{
// All constructor parameters are required to have some matching member.
if (matchingMemberProperty == null)
throw new JsonSerializationException(string.Format("No matching member for constructor parameter \"{0}\" of type \"{1}\".", parameterInfo, parameterInfo.Member.DeclaringType));
var property = base.CreatePropertyFromConstructorParameter(matchingMemberProperty, parameterInfo);
if (property != null && matchingMemberProperty != null)
{
if (!matchingMemberProperty.IsRequiredSpecified) // If the member is already explicitly marked with some Required attribute, don't override it.
{
Required required;
if (matchingMemberProperty.PropertyType != null && (matchingMemberProperty.PropertyType.IsValueType && Nullable.GetUnderlyingType(matchingMemberProperty.PropertyType) == null))
{
required = Required.Always;
}
else
{
required = Required.AllowNull;
}
// It turns out to be necessary to mark the original matchingMemberProperty as required.
property.Required = matchingMemberProperty.Required = required;
}
}
return property;
}
}
Pour l'utiliser, construisez le résolveur:
static IContractResolver resolver = new ConstructorParametersRequiredContractResolver();
Et test unitaire comme suit:
var settings = new JsonSerializerSettings
{
ContractResolver = resolver,
};
JsonConvert.DeserializeObject<Foo>("{\"FooField\":42}", settings);
Notez que vous souhaiterez peut-être mettre en cache et réutiliser le résolveur de contrat pour de meilleures performances .
Démo violon # 2 ici .