Mantieni le maiuscole durante la serializzazione dei dizionari


92

Ho un progetto Web Api configurato in questo modo:

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

Tuttavia, desidero che l'involucro delle chiavi del dizionario rimanga invariato. c'è qualche attributo in Newtonsoft.Jsonche posso usare per una classe per indicare che voglio che il maiuscolo rimanga invariato durante la serializzazione?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}

1
Hai provato il resolver predefinito?
Matteo

1
@ Matteo No, non l'ho fatto; puoi spiegare con un esempio come sarebbe il codice? Nota, voglio ancora la serializzazione del caso Camel per tutte le mie richieste di API web, voglio solo la serializzazione personalizzata per una classe (o forse per qualsiasi chiave del dizionario).
zafeiris.m

Risposte:


133

Non esiste un attributo per farlo, ma puoi farlo personalizzando il resolver.

Vedo che stai già utilizzando un file CamelCasePropertyNamesContractResolver. Se si ricava una nuova classe resolver da quella e si sovrascrive il CreateDictionaryContract()metodo, è possibile fornire una DictionaryKeyResolverfunzione sostitutiva che non modifica i nomi delle chiavi.

Ecco il codice di cui avresti bisogno:

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

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Demo:

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; }
}

Ecco l'output di quanto sopra. Si noti che tutti i nomi delle proprietà della classe sono in maiuscolo, ma le chiavi del dizionario hanno mantenuto la loro maiuscola originale.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}

2
Cordiali saluti, PropertyNameResolver è ora obsoleto. Sembra che contract.DictionaryKeyResolver = key => key;funzioni bene.
John Gietzen

1
Questo è ancora MOLTO rilevante con i tipi anonimi, specialmente quando vogliamo che la maggior parte della struttura abbia un involucro di cammello, ma non vogliamo che le chiavi all'interno dei dizionari siano camelizzate.
Chris Schaller

Assolutamente d'accordo con Chris. Sono stato costretto a passare attraverso i cerchi nel mio JavaScript solo perché non posso impedire che i dizionari siano camelCased. Si scopre che una riga di codice risolverà questo problema (e renderà il mio JavaScript molto più semplice)!
Stephen Chung

@BrianRogers Funziona alla grande! Tuttavia, sai se posso condizionare usando my DictionaryKeyResolversolo se la mia proprietà Dictionary ha qualche attributo personalizzato?
Mugen

@ Mugen Non dalla parte superiore della mia testa. Consiglierei di chiederlo come nuova domanda. Puoi ricollegarti a questa domanda se devi fornire un contesto.
Brian Rogers

67

Json.NET 9.0.1 ha introdotto la NamingStrategygerarchia delle classi per gestire questo tipo di problema. Estrae la logica per la rimappatura algoritmica dei nomi di proprietà dal risolutore di contratti a una classe leggera e separata che consente di controllare se le chiavi del dizionario , i nomi di proprietà specificati in modo esplicito e i nomi di dati di estensione (nella 10.0.1 ) vengono rimappati.

Utilizzando DefaultContractResolvere impostando NamingStrategyun'istanza di CamelCaseNamingStrategypuoi generare JSON con nomi di proprietà in maiuscolo e minuscole e chiavi di dizionario non modificate impostandolo in JsonSerializerSettings.ContractResolver:

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

Appunti:

  • L'attuale implementazione di CamelCasePropertyNamesContractResolverspecifica anche che i membri .Net con nomi di proprietà specificati esplicitamente (ad esempio quelli in cui JsonPropertyAttribute.PropertyNameè stato impostato) dovrebbero avere i loro nomi rimappati:

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

    Quanto sopra resolverpreserva questo comportamento. Se non vuoi questo, imposta OverrideSpecifiedNames = false.

  • Json.NET ha diverse strategie di denominazione integrate, tra cui:

    1. CamelCaseNamingStrategy. Una strategia di denominazione dei casi di cammello che contiene la logica di rimappatura dei nomi incorporata in precedenza CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Una strategia di denominazione dei casi di serpente .
    3. DefaultNamingStrategy. La strategia di denominazione predefinita. I nomi delle proprietà e le chiavi del dizionario non vengono modificati.

    Oppure puoi crearne uno tuo ereditando dalla classe base astratta NamingStrategy.

  • Sebbene sia anche possibile modificare il NamingStrategydi un'istanza di CamelCasePropertyNamesContractResolver, poiché quest'ultima condivide le informazioni sul contratto a livello globale tra tutte le istanze di ciascun tipo , ciò può portare a effetti collaterali imprevisti se l'applicazione tenta di utilizzare più istanze di CamelCasePropertyNamesContractResolver. Nessun problema di questo tipo esiste DefaultContractResolver, quindi è più sicuro da usare quando è richiesta una qualsiasi personalizzazione della logica dell'involucro.


Questa soluzione non funziona per una proprietà come public Dictionary<string, Dictionary<string, string>> Values { get; set; }. Fa ancora camelCase per le chiavi interne del dizionario.
hikalkan

@hikalkan - anche se non sono riuscito a riprodurre il tuo problema esatto, sono riuscito a trovare un problema utilizzando più istanze di CamelCasePropertyNamesContractResolver. Fondamentalmente NamingStrategyper il primo influenzerebbe i contratti generati dal secondo. Questo potrebbe essere quello che stai vedendo. Prova invece il nuovo consiglio e fammi sapere se risolve il tuo problema.
dbc

1
Esiste un flessibile NamingStrategy, in modo che sia in grado di analizzare sia il caso del cammello che il caso del pascal?
Shimmy Weitzhandler

@dbc Nell'esempio di codice iniziale, cosa configdovrebbe essere?
Ryan Lundy

@RyanLundy - ho copiato dalla domanda iniziale, che ha mostrato la seguente riga di codice: config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Sembra essere l'API Web MVC 4 HttpConfiguration, vedere Come impostare JsonSerializerSettings personalizzato per Json.NET nell'API Web MVC 4? .
dbc

12

Questa è una risposta molto carina. Ma perché non sovrascrivere semplicemente il ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

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

        #endregion
    }

Molto conciso. Grazie per la condivisione.
Abu Abdullah

1

La risposta selezionata è perfetta ma immagino che nel momento in cui lo scrivo, il risolutore di contratti deve cambiare in qualcosa di simile perché DictionaryKeyResolver non esiste più :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }

5
In realtà è vero il contrario. Devi utilizzare una vecchia versione di Json.Net. DictionaryKeyResolverè stato aggiunto nella versione 7.0.1 ed è PropertyNameResolverstato contrassegnato come obsoleto.
Brian Rogers
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.