Зберігайте регістр при серіалізації словників


92

У мене проект Web Api налаштований так:

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

Однак я хочу, щоб корпус ключів словника залишався незмінним. чи є якийсь атрибут, який Newtonsoft.Jsonя можу використовувати для класу для позначення того, що я хочу, щоб кожух залишався незмінним під час серіалізації?

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

1
Ви пробували вирішувач за замовчуванням?
Matthew

1
@Matthew Ні, я не мав; Ви можете пояснити на прикладі, як би виглядав код? Зверніть увагу, я все ще хочу серіалізацію випадку Camel для всіх моїх веб-запитів API, я просто хочу власну серіалізацію для класу (або, можливо, для будь-яких ключів словника).
zafeiris.m

Відповіді:


132

Для цього немає атрибута, але ви можете зробити це, налаштувавши вирішувач.

Я бачу, що ви вже використовуєте CamelCasePropertyNamesContractResolver. Якщо ви отримаєте новий клас резольвера з цього і перевизначите CreateDictionaryContract()метод, ви можете надати функцію-заміну DictionaryKeyResolver, яка не змінює імена ключів.

Ось код, який вам знадобиться:

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

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Демо:

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

Ось вихідні дані з вищезазначеного. Зверніть увагу, що всі назви властивостей класу мають верблюд, але ключі словника зберегли оригінальну оболонку.

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

2
FYI, PropertyNameResolver тепер застарілий. Здається, це contract.DictionaryKeyResolver = key => key;працює чудово.
Джон Гітцен,

1
Це все ще ДУЖЕ актуально для анонімних типів, особливо коли ми хочемо, щоб корпус верблюда більшу частину конструкції, але не хочемо, щоб ключі всередині словників були камелізованими.
Chris Schaller

Абсолютно згоден з Крісом. Мене змусили переглядати обручі у своєму JavaScript лише тому, що я не можу утримати словники від того, щоб їх не було. Виявляється, один рядок коду вирішить цю проблему (і спростить мій JavaScript)!
Стівен Чунг

@BrianRogers Чудово працює! Однак, чи знаєте ви, чи можу я умовою користуватися своїм, DictionaryKeyResolverлише якщо у моїй властивості Dictionary є якийсь спеціальний атрибут?
Mugen

@Mugen Не з голови. Я б порекомендував задати це як нове запитання. Ви можете повернутися до цього питання, якщо вам потрібно надати контекст.
Брайан Роджерс

67

Json.NET 9.0.1 представив NamingStrategyієрархію класів для вирішення такого роду проблем. Він витягує логіку алгоритмічного перепризначення імен властивостей із засобу вирішення контрактів до окремого, полегшеного класу, що дозволяє контролювати, чи переназначаються ключі словника , явно вказані імена властивостей та імена даних розширень10.0.1 ).

Використовуючи DefaultContractResolverта встановлюючи NamingStrategyекземпляр, CamelCaseNamingStrategyви можете генерувати JSON з назвою властивостей верблюдів та незміненими ключами словника, встановивши його в JsonSerializerSettings.ContractResolver:

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

Примітки:

  • Поточна реалізація CamelCasePropertyNamesContractResolverтакож визначає, що члени .Net з явно вказаними іменами властивостей (наприклад, ті, де JsonPropertyAttribute.PropertyNameбуло встановлено) повинні мати свої імена перепризначеними:

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

    Вищевказане resolverзберігає таку поведінку. Якщо ви цього не хочете, встановіть OverrideSpecifiedNames = false.

  • Json.NET має кілька вбудованих стратегій іменування, включаючи:

    1. CamelCaseNamingStrategy. Стратегія іменування випадків верблюда, що містить логіку перепризначення імен, раніше вбудовану в CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Змія випадку стратегія іменування.
    3. DefaultNamingStrategy. Стратегія імен за замовчуванням. Назви властивостей та ключі словника не змінюються.

    Або ви можете створити свій власний, успадкувавши його від абстрактного базового класу NamingStrategy.

  • Хоча також можливо змінити NamingStrategyекземпляр CamelCasePropertyNamesContractResolver, оскільки останній обмінюється інформацією про договори глобально у всіх екземплярах кожного типу , це може призвести до несподіваних побічних ефектів, якщо ваша програма намагається використовувати кілька екземплярів CamelCasePropertyNamesContractResolver. З такою проблемою не існує DefaultContractResolver, тому безпечніше використовувати, коли потрібна будь-яка настройка логіки кожуха.


Це рішення не працює для такого майна, як public Dictionary<string, Dictionary<string, string>> Values { get; set; }. Це все ще робить camelCase для внутрішніх ключів словника.
hikalkan

@hikalkan - хоча я не міг відтворити вашу точну проблему, мені вдалося знайти проблему, використовуючи кілька екземплярів CamelCasePropertyNamesContractResolver. В основному NamingStrategyдля першого впливає на контракти, породжувані другим. Це може бути те, що ви бачите. Спробуйте натомість нову рекомендацію та повідомте мене, якщо проблема вирішить вашу проблему.
dbc

1
Чи існує гнучка NamingStrategy, щоб вона могла аналізувати як футляр з верблюдом, так і футляр з паскалем?
Shimmy Weitzhandler

@dbc Що в початковому зразку коду configмає бути?
Райан Ланді,

@RyanLundy - Я скопіював його з початкового питання, який показав наступний рядок коду: config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Схоже, це веб-API MVC 4 HttpConfiguration, див. Розділ Як встановити власні налаштування JsonSerializerSettings для Json.NET у веб-API MVC 4? .
dbc

12

Це дуже приємна відповідь. Але чому б просто не відмінити ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

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

        #endregion
    }

Набагато стислі. Thx для обміну.
Абу Абдулла

1

Вибрана відповідь ідеальна, але я думаю, що до того часу, як я це наберу, вирішувач контракту повинен змінитись на щось подібне, оскільки DictionaryKeyResolver вже не існує :)

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

4
Насправді все навпаки. Ви повинні використовувати стару версію Json.Net. DictionaryKeyResolverбуло додано у версію 7.0.1 та PropertyNameResolverпозначено як застаріле.
Брайан Роджерс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.