Сохраняйте регистр при сериализации словарей

92

У меня есть проект Web Api, настроенный следующим образом:

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

Однако я хочу, чтобы корпус словарных ключей оставался неизменным. Есть ли в Newtonsoft.Jsonклассе какой-либо атрибут, который я могу использовать для обозначения того, что я хочу, чтобы корпус оставался неизменным во время сериализации?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}
zafeiris.m
источник
1
Вы пробовали резолвер по умолчанию?
Мэтью
1
@ Мэтью Нет, я не знал; можете ли вы объяснить на примере, как будет выглядеть код? Обратите внимание, я все еще хочу сериализацию случая Camel для всех моих запросов веб-API, мне просто нужна настраиваемая сериализация для класса (или, возможно, для любых ключей словаря).
zafeiris.m

Ответы:

133

Для этого нет атрибута, но вы можете сделать это, настроив преобразователь.

Я вижу, что вы уже используете 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
К вашему сведению, PropertyNameResolver теперь устарел. Кажется, все contract.DictionaryKeyResolver = key => key;работает нормально.
Джон Гитцен
1
Это все еще ОЧЕНЬ актуально для анонимных типов, особенно когда мы хотим, чтобы для большей части структуры использовалась верблюжья оболочка, но мы не хотим, чтобы ключи внутри словарей были преобразованы в верблюд.
Крис Шаллер,
Абсолютно согласен с Крисом. Я был вынужден пройти через обручи в моем 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, поэтому его безопаснее использовать, когда требуется настройка логики корпуса.

dbc
источник
Это решение не работает для такого свойства, как 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 07
12

Это очень хороший ответ. Но почему бы просто не переопределить ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

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

        #endregion
    }
Дмитрий
источник
Очень кратко. Спасибо за обмен.
Абу Абдулла
1

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

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }
Сен
источник
5
На самом деле верно обратное. Вы должны использовать старую версию Json.Net. DictionaryKeyResolverбыл добавлен в версии 7.0.1 и PropertyNameResolverпомечен как устаревший.
Брайан Роджерс