Zachowaj wielkość liter podczas serializacji słowników

92

Mam projekt Web Api konfigurowany w następujący sposób:

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

Jednak chcę, aby wielkość liter w klawiszach słownika pozostała niezmieniona. czy jest jakiś atrybut, Newtonsoft.Jsonktórego mogę użyć do klasy, aby wskazać, że chcę, aby wielkość liter pozostała niezmieniona podczas serializacji?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}
zafeiris.m
źródło
1
Czy próbowałeś domyślnego resolvera?
Matthew
1
@Matthew Nie, nie mam; czy możesz wyjaśnić na przykładzie, jak wyglądałby kod? Uwaga, nadal chcę serializacji skrzynek Camel dla wszystkich moich żądań interfejsu API sieci Web, chcę tylko niestandardowej serializacji dla klasy (lub może dla dowolnych kluczy słownika).
zafeiris.m

Odpowiedzi:

133

Nie ma atrybutu, który mógłby to zrobić, ale możesz to zrobić, dostosowując program rozpoznawania nazw.

Widzę, że już używasz CamelCasePropertyNamesContractResolver. Jeśli wyprowadzisz z tego nową klasę przelicznika i przesłonisz CreateDictionaryContract()metodę, możesz zapewnić funkcję zastępczą DictionaryKeyResolver, która nie zmienia nazw kluczy.

Oto kod, którego potrzebujesz:

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

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Próbny:

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

Oto wynik z powyższego. Zwróć uwagę, że wszystkie nazwy właściwości klas mają wielkość liter wielbłąda, ale klucze słownika zachowały swoją oryginalną wielkość liter.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}
Brian Rogers
źródło
2
FYI, PropertyNameResolver jest teraz przestarzały. Wygląda na to, że contract.DictionaryKeyResolver = key => key;działa dobrze.
John Gietzen,
1
Jest to nadal BARDZO istotne w przypadku typów anonimowych, zwłaszcza gdy chcemy, aby obudowa wielbłąda dla większości konstrukcji, ale nie chcemy, aby klucze w słownikach były kamelizowane.
Chris Schaller
Całkowicie zgadzam się z Chrisem. Zostałem zmuszony do pokonywania przeszkód w moim JavaScript tylko dlatego, że nie mogę powstrzymać słowników przed byciem camelCased. Okazuje się, że jedna linia kodu rozwiąże ten problem (i znacznie uprości mój JavaScript)!
Stephen Chung
@BrianRogers Działa świetnie! Jednak czy wiesz, czy mogę warunkować, używając mojego DictionaryKeyResolvertylko wtedy, gdy moja właściwość Dictionary ma jakiś niestandardowy atrybut?
Mugen
@Mugen Nie z mojej głowy. Poleciłbym zadać to jako nowe pytanie. Możesz podać link do tego pytania, jeśli chcesz podać kontekst.
Brian Rogers
67

Json.NET 9.0.1 wprowadził NamingStrategyhierarchię klas do obsługi tego rodzaju problemów. Wyodrębnia logikę algorytmicznego ponownego mapowania nazw właściwości z mechanizmu rozpoznawania kontraktu do oddzielnej, lekkiej klasy, która umożliwia kontrolę nad tym, czy klucze słownika , jawnie określone nazwy właściwości i nazwy danych rozszerzeń (w wersji 10.0.1 ) są ponownie mapowane.

Używając DefaultContractResolveri ustawiając NamingStrategyna wystąpienie CamelCaseNamingStrategyyou, możesz wygenerować JSON z nazwami właściwości w postaci wielbłądów i niezmodyfikowanymi kluczami słownika, ustawiając je w JsonSerializerSettings.ContractResolver:

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

Uwagi:

  • Bieżąca implementacja CamelCasePropertyNamesContractResolverokreśla również, że członkowie .Net z wyraźnie określonymi nazwami właściwości (np. Te , dla których JsonPropertyAttribute.PropertyNamezostały ustawione) powinny mieć przemapowane nazwy:

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

    Powyższe resolverzachowuje to zachowanie. Jeśli tego nie chcesz, ustaw OverrideSpecifiedNames = false.

  • Json.NET ma kilka wbudowanych strategii nazewnictwa, w tym:

    1. CamelCaseNamingStrategy. Strategia nazewnictwa przypadków wielbłądów, która zawiera logikę przemapowywania nazw wcześniej osadzoną w CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Przypadek wąż strategia nazewnictwa.
    3. DefaultNamingStrategy. Domyślna strategia nazewnictwa. Nazwy właściwości i klucze słownika pozostają niezmienione.

    Możesz też utworzyć własną, dziedzicząc po abstrakcyjnej klasie bazowej NamingStrategy.

  • Chociaż można również zmodyfikować NamingStrategywystąpienie programu CamelCasePropertyNamesContractResolver, ponieważ ten ostatni udostępnia informacje o kontrakcie globalnie we wszystkich wystąpieniach każdego typu , może to prowadzić do nieoczekiwanych skutków ubocznych, jeśli aplikacja spróbuje użyć wielu wystąpień CamelCasePropertyNamesContractResolver. Nie ma takiego problemu DefaultContractResolver, więc bezpieczniej jest używać go, gdy wymagane jest jakiekolwiek dostosowanie logiki obudowy.

dbc
źródło
To rozwiązanie nie działa w przypadku takiej nieruchomości public Dictionary<string, Dictionary<string, string>> Values { get; set; }. Nadal robi camelCase dla kluczy słownika wewnętrznego.
hikalkan
@hikalkan - chociaż nie mogłem dokładnie odtworzyć Twojego problemu, udało mi się znaleźć problem podczas korzystania z wielu wystąpień CamelCasePropertyNamesContractResolver. Zasadniczo NamingStrategypierwszy wpływ miałby na kontrakty generowane przez drugi. To może być to, co widzisz. Zamiast tego wypróbuj nowe zalecenie i daj mi znać, jeśli rozwiąże problem.
dbc
1
Czy jest elastyczny NamingStrategy, aby był w stanie przeanalizować zarówno skrzynkę wielbłąda, jak i skrzynkę pascalową?
Shimmy Weitzhandler,
@dbc Co configpowinno być w początkowym przykładzie kodu ?
Ryan Lundy
@RyanLundy - skopiowałem go od pierwszego pytania, które wykazały następujący wiersz kodu: config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Wygląda na internetowy interfejs API MVC 4 HttpConfiguration, zobacz Jak ustawić niestandardowe JsonSerializerSettings dla Json.NET w MVC 4 Web API? .
dbc
12

To bardzo miła odpowiedź. Ale dlaczego po prostu nie zastąpić ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

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

        #endregion
    }
Dmitriy
źródło
Bardzo zwięzłe. Dzięki za udostępnienie.
Abu Abdullah
1

Wybrana odpowiedź jest idealna, ale myślę, że w momencie, gdy to piszę, narzędzie do rozpoznawania kontraktu musi zmienić się na coś takiego, ponieważ DictionaryKeyResolver już nie istnieje :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }
Wrz
źródło
5
Właściwie jest odwrotnie. Musisz używać starej wersji Json.Net. DictionaryKeyResolverzostał dodany w wersji 7.0.1 i PropertyNameResolverzostał oznaczony jako przestarzały.
Brian Rogers