Gardez la casse lors de la sérialisation des dictionnaires

92

J'ai un projet Web Api configuré comme ceci:

config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

Cependant, je veux que le boîtier des clés du dictionnaire reste inchangé. y a-t-il un attribut que Newtonsoft.Jsonje peux utiliser pour une classe pour indiquer que je veux que la casse reste inchangée pendant la sérialisation?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}
zafeiris.m
la source
1
Avez-vous essayé le résolveur par défaut?
Matthew
1
@Matthew Non, je ne l'ai pas fait; pouvez-vous expliquer avec un exemple à quoi ressemblerait le code? Remarque, je veux toujours une sérialisation de cas Camel pour toutes mes requêtes API Web, je veux juste une sérialisation personnalisée pour une classe (ou peut-être pour toutes les clés de dictionnaire).
zafeiris.m

Réponses:

133

Il n'y a pas d'attribut pour faire cela, mais vous pouvez le faire en personnalisant le résolveur.

Je vois que vous utilisez déjà un fichier CamelCasePropertyNamesContractResolver. Si vous dérivez une nouvelle classe de résolveur à partir de cela et remplacez la CreateDictionaryContract()méthode, vous pouvez fournir une DictionaryKeyResolverfonction de remplacement qui ne modifie pas les noms de clé.

Voici le code dont vous auriez besoin:

class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
{
    protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
    {
        JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Démo:

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Foo
        {
            AnIntegerProperty = 42,
            HTMLString = "<html></html>",
            Dictionary = new Dictionary<string, string>
            {
                { "WHIZbang", "1" },
                { "FOO", "2" },
                { "Bar", "3" },
            }
        };

        JsonSerializerSettings settings = new JsonSerializerSettings
        {
            ContractResolver = new CamelCaseExceptDictionaryKeysResolver(),
            Formatting = Formatting.Indented
        };

        string json = JsonConvert.SerializeObject(foo, settings);
        Console.WriteLine(json);
    }
}

class Foo
{
    public int AnIntegerProperty { get; set; }
    public string HTMLString { get; set; }
    public Dictionary<string, string> Dictionary { get; set; }
}

Voici la sortie de ce qui précède. Notez que tous les noms de propriété de classe sont en camel, mais les clés du dictionnaire ont conservé leur casse d'origine.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}
Brian Rogers
la source
2
Pour info, PropertyNameResolver est désormais obsolète. Il semble que cela contract.DictionaryKeyResolver = key => key;fonctionne très bien.
John Gietzen
1
Ceci est toujours TRÈS pertinent avec les types anonymes, en particulier lorsque nous voulons un boîtier de chameau pour la majeure partie de la structure, mais ne voulons pas que les clés à l'intérieur des dictionnaires soient camélisées.
Chris Schaller
Absolument d'accord avec Chris. J'ai été obligé de passer par des cerceaux dans mon JavaScript simplement parce que je ne peux pas empêcher les dictionnaires d'être camelCased. Il s'avère qu'une seule ligne de code résoudra ce problème (et rendra mon JavaScript beaucoup plus simple)!
Stephen Chung
@BrianRogers Fonctionne très bien! Cependant, savez-vous si je peux conditionner en utilisant mon DictionaryKeyResolveruniquement si ma propriété Dictionary possède un attribut personnalisé?
Mugen
@Mugen Pas du tout de ma tête. Je recommanderais de poser cela comme une nouvelle question. Vous pouvez revenir à cette question si vous avez besoin de fournir un contexte.
Brian Rogers
67

Json.NET 9.0.1 a introduit la NamingStrategyhiérarchie des classes pour gérer ce type de problème. Il extrait la logique du remappage algorithmique des noms de propriété du résolveur de contrat vers une classe séparée et légère qui permet de contrôler si les clés de dictionnaire , les noms de propriété explicitement spécifiés et les noms de données d'extension (dans 10.0.1 ) sont remappés.

En utilisant DefaultContractResolveret en définissant NamingStrategysur une instance de, CamelCaseNamingStrategyvous pouvez générer du JSON avec des noms de propriété en cas de camel et des clés de dictionnaire non modifiées en le définissant dans JsonSerializerSettings.ContractResolver:

var resolver = new DefaultContractResolver
{
    NamingStrategy = new CamelCaseNamingStrategy
    {
        ProcessDictionaryKeys = false,
        OverrideSpecifiedNames = true
    }
};
config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = resolver;

Remarques:

  • L'implémentation actuelle de CamelCasePropertyNamesContractResolverspécifie également que les membres .Net avec des noms de propriété explicitement spécifiés (par exemple ceux où JsonPropertyAttribute.PropertyNamea été défini) doivent avoir leurs noms remappés:

    public CamelCasePropertyNamesContractResolver()
    {
        NamingStrategy = new CamelCaseNamingStrategy
        {
            ProcessDictionaryKeys = true,
            OverrideSpecifiedNames = true
        };
    }
    

    Ce qui précède resolverpréserve ce comportement. Si vous ne le souhaitez pas, réglez OverrideSpecifiedNames = false.

  • Json.NET a plusieurs stratégies de dénomination intégrées, notamment:

    1. CamelCaseNamingStrategy. Une stratégie de dénomination de cas de chameau qui contient la logique de remappage de nom précédemment intégrée dans CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Une stratégie de dénomination de cas de serpent .
    3. DefaultNamingStrategy. La stratégie de dénomination par défaut. Les noms de propriété et les clés de dictionnaire restent inchangés.

    Ou, vous pouvez créer le vôtre en héritant de la classe de base abstraite NamingStrategy.

  • Bien qu'il soit également possible de modifier le NamingStrategyd'une instance de CamelCasePropertyNamesContractResolver, étant donné que cette dernière partage les informations de contrat globalement sur toutes les instances de chaque type , cela peut entraîner des effets secondaires inattendus si votre application tente d'utiliser plusieurs instances de CamelCasePropertyNamesContractResolver. Aucun problème de ce type n'existe avec DefaultContractResolver, il est donc plus sûr à utiliser lorsqu'une personnalisation de la logique de boîtier est requise.

dbc
la source
Cette solution ne fonctionne pas pour une propriété comme public Dictionary<string, Dictionary<string, string>> Values { get; set; }. Il fait toujours camelCase pour les clés du dictionnaire interne.
hikalkan
@hikalkan - bien que je ne puisse pas reproduire votre problème exact, j'ai pu trouver un problème lors de l'utilisation de plusieurs instances de CamelCasePropertyNamesContractResolver. Fondamentalement, NamingStrategyle premier influencerait les contrats générés par le second. C'est peut- être ce que vous voyez. Essayez plutôt la nouvelle recommandation et faites-moi savoir si elle résout votre problème.
dbc
1
Y a-t-il un flexible NamingStrategy, de sorte qu'il soit capable d'analyser à la fois l'étui camel et l'étui pascal?
Shimmy Weitzhandler
@dbc Dans l'exemple de code initial, qu'est-ce qui est configcensé être?
Ryan Lundy
@RyanLundy - Je l' ai copié de la question initiale, qui a montré la ligne de code suivante: config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Il semble s'agir de l'API Web MVC 4 HttpConfiguration, consultez Comment définir des paramètres JsonSerializerSettings personnalisés pour Json.NET dans l'API Web MVC 4? .
dbc
12

C'est une très belle réponse. Mais pourquoi ne pas simplement remplacer le ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

        protected override string ResolveDictionaryKey(string dictionaryKey)
        {
            return dictionaryKey;
        }

        #endregion
    }
Dmitriy
la source
Beaucoup laconique. Thx pour le partage.
Abu Abdullah
1

La réponse sélectionnée est parfaite mais je suppose qu'au moment où je tape ceci, le résolveur de contrat doit changer pour quelque chose comme ça parce que DictionaryKeyResolver n'existe plus :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }
SEP
la source
5
En fait, l'inverse est vrai. Vous devez utiliser une ancienne version de Json.Net. DictionaryKeyResolvera été ajouté dans la version 7.0.1 et a PropertyNameResolverété marqué comme obsolète.
Brian Rogers