web-dev-qa-db-fra.com

Désérialisation de JSON à l'aide de JsonSerializer.DeserializeAsync n'utilise pas mon JsonConverter

Un serveur renvoie une valeur de chaîne JSON qui est une chaîne de requête URL:

{
    "parameters": "key1=value1&key2=value2"
}

J'ai une propriété configurée pour recevoir cela et le convertir en Dictionary dans le cadre du processus de désérialisation:

Propriété avec l'attribut JsonConverter:

[JsonConverter(typeof(QueryStringToDictionaryJsonConverter))]
public Dictionary<string, string> Parameters { get; set; }

Convertisseur:

public class QueryStringToDictionaryJsonConverter : JsonConverter<Dictionary<string, string>> {

    public override Dictionary<string, string> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {

        var queryString = reader.GetString();
        if (string.IsNullOrEmpty(queryString)) return null;

        return QueryHelpers.ParseQuery(queryString).ToDictionary(e => e.Key, e => string.Join(",", e.Value.ToArray()));

    }

    ...
}

Cela devrait fonctionner.

Mais ça n'arrive même pas à mon convertisseur.

D'après ce que je peux dire, JsonSerializer.DeserializeAsync<T>(myJson) voit que le type de propriété est un Dictionary, et donc il essaie d'analyser la valeur en tant que telle par lui-même, et échoue (l'exception résultante est un 'cast invalide' alors qu'il essaie de GetEnumerable() etc). Un point d'arrêt dans mon convertisseur n'est même jamais touché.

Je peux le faire fonctionner en faisant de la propriété un object puis en le convertissant en Dictionary plus tard où il est utilisé, mais c'est une solution laide.

Existe-t-il un moyen de forcer JsonSerializer.DeserializeAsync<T>(myJson) à simplement utiliser mon convertisseur, sans qu'il essaie d'être intelligent par lui-même?

(J'utilise System.Text.Json de Microsoft dans .NET Core 3)

4
Ross

OK, cela pourrait donc être un bogue dans System.Text.Json.

Voici la solution de contournement que j'utilise actuellement pour quiconque a besoin d'une solution.

Tout d'abord, j'ai configuré deux propriétés pour la désérialisation, en utilisant [JsonPropertyName] et [JsonIgnore]:

[JsonPropertyName("parameters"), JsonConverter(typeof(QueryStringToDictionaryJsonConverter))]
public object ParametersObject { get; set; }

[JsonIgnore]
public Dictionary<string, string> Parameters => ParametersObject as Dictionary<string, string>;

Et puis dans le JsonConverter, j'autorise object comme type:

public override bool CanConvert(Type typeToConvert) {
    if (typeToConvert == typeof(object)) return true;
    return base.CanConvert(typeToConvert);
}

Les consommateurs de ma classe désérialisée utilisent simplement la propriété Parameters, qui continuera à bien fonctionner si et quand ce bogue est corrigé et je change la classe comme je le voudrais.

0
Ross

Je créerais un wrapper et créerais un convertisseur pour le wrapper.

[JsonConverter( typeof( QueryStringDictionaryConverter ) )]
class QueryStringDictionary : Dictionary<string,string> { }

class QueryStringDictionaryConverter : JsonConverter<QueryStringDictionary>
{
    ... 
}

class MyClass
{
    public QueryStringDictionary Parameters { get; set; }
}

Vous pouvez également utiliser JsonSerializerOptions

class MyOtherClass
{
   public Dictionary<string,string> Parameters { get; set; }
}
MyOtherClass Deserialize( string json )
{
    var options = new JsonSerializerOptions
    {
        Converters = { new QueryStringToDictionaryJsonConverter() }
    };
    return JsonSerializer.Deserialize<MyOtherClass>( json, options );  
} 

Un problème potentiel avec cette approche est que le convertisseur serait utilisé sur tous les Dictionary<string,string> propriétés, qui peuvent ne pas être voulues. Cela fonctionnerait bien pour l'exemple simple de la question d'origine.

0
phizch