Mantener mayúsculas y minúsculas al serializar diccionarios

92

Tengo un proyecto de Web Api que se configura así:

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

Sin embargo, quiero que las mayúsculas y minúsculas del diccionario permanezcan sin cambios. ¿Hay algún atributo Newtonsoft.Jsonque pueda usar en una clase para indicar que quiero que la carcasa permanezca sin cambios durante la serialización?

public class SomeViewModel
{
    public Dictionary<string, string> Data { get; set; }    
}
zafeiris.m
fuente
1
¿Has probado el solucionador predeterminado?
Mateo
1
@Matthew No, no lo he hecho; ¿Puede explicar con un ejemplo cómo se vería el código? Tenga en cuenta que todavía quiero la serialización de casos de Camel para todas mis solicitudes de API web, solo quiero una serialización personalizada para una clase (o tal vez para cualquier clave de diccionario).
zafeiris.m

Respuestas:

133

No hay un atributo para hacer esto, pero puede hacerlo personalizando el resolutor.

Veo que ya estás usando un CamelCasePropertyNamesContractResolver. Si deriva una nueva clase de resolución de eso y anula el CreateDictionaryContract()método, puede proporcionar una DictionaryKeyResolverfunción sustituta que no cambie los nombres de las claves.

Aquí está el código que necesitaría:

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

        contract.DictionaryKeyResolver = propertyName => propertyName;

        return contract;
    }
}

Manifestación:

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

Aquí está el resultado de lo anterior. Observe que todos los nombres de propiedad de la clase están en mayúsculas, pero las claves del diccionario han conservado su formato original.

{
  "anIntegerProperty": 42,
  "htmlString": "<html></html>",
  "dictionary": {
    "WHIZbang": "1",
    "FOO": "2",
    "Bar": "3"
  }
}
Brian Rogers
fuente
2
FYI, PropertyNameResolver ahora está obsoleto. Parece que contract.DictionaryKeyResolver = key => key;funciona bien.
John Gietzen
1
Esto sigue siendo MUY relevante con los tipos anónimos, especialmente cuando queremos una carcasa camel para la mayor parte de la estructura, pero no queremos que las claves dentro de los diccionarios estén camelizadas.
Chris Schaller
Absolutamente de acuerdo con Chris. Me he visto obligado a pasar por aros en mi JavaScript solo porque no puedo evitar que los diccionarios estén basados ​​en camello. ¡Resulta que una línea de código resolverá este problema (y hará que mi JavaScript sea mucho más simple)!
Stephen Chung
@BrianRogers ¡Funciona muy bien! Sin embargo, ¿sabe si puedo condicionar usando mi DictionaryKeyResolversolo si mi propiedad de Diccionario tiene algún atributo personalizado?
Mugen
@Mugen No es algo que se me ocurra. Recomendaría hacer eso como una nueva pregunta. Puede vincular a esta pregunta si necesita proporcionar contexto.
Brian Rogers
67

Json.NET 9.0.1 introdujo la NamingStrategyjerarquía de clases para manejar este tipo de problemas. Extrae la lógica para la reasignación algorítmica de los nombres de propiedad del solucionador de contratos a una clase ligera separada que permite controlar si las claves del diccionario , los nombres de propiedad especificados explícitamente y los nombres de datos de extensión (en 10.0.1 ) se reasignan.

Al usar DefaultContractResolvery configurar NamingStrategyuna instancia de CamelCaseNamingStrategy, puede generar JSON con nombres de propiedad en mayúsculas y minúsculas y claves de diccionario sin modificar configurándolo en JsonSerializerSettings.ContractResolver:

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

Notas:

  • La implementación actual de CamelCasePropertyNamesContractResolvertambién especifica que los miembros .Net con nombres de propiedad explícitamente especificados (por ejemplo, aquellos donde JsonPropertyAttribute.PropertyNamese ha establecido) deben tener sus nombres reasignados:

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

    Lo anterior resolverconserva este comportamiento. Si no quieres esto, configúralo OverrideSpecifiedNames = false.

  • Json.NET tiene varias estrategias de nomenclatura integradas que incluyen:

    1. CamelCaseNamingStrategy. Una estrategia de nomenclatura de casos de camello que contiene la lógica de reasignación de nombres que antes estaba incorporada CamelCasePropertyNamesContractResolver.
    2. SnakeCaseNamingStrategy. Una estrategia de nomenclatura de casos de serpientes .
    3. DefaultNamingStrategy. La estrategia de nomenclatura predeterminada. Los nombres de propiedad y las claves del diccionario no se modifican.

    O puede crear el suyo propio heredando de la clase base abstracta NamingStrategy.

  • Si bien también es posible modificar el NamingStrategyde una instancia de CamelCasePropertyNamesContractResolver, dado que este último comparte la información del contrato de forma global en todas las instancias de cada tipo , esto puede provocar efectos secundarios inesperados si su aplicación intenta usar varias instancias de CamelCasePropertyNamesContractResolver. No existe tal problema con DefaultContractResolver, por lo que es más seguro usarlo cuando se requiere cualquier personalización de la lógica de carcasa.

dbc
fuente
Esta solución no funciona para una propiedad como public Dictionary<string, Dictionary<string, string>> Values { get; set; }. Todavía hace camelCase para las claves internas del diccionario.
hikalkan
@hikalkan: aunque no pude reproducir su problema exacto, pude encontrar un problema al usar varias instancias de CamelCasePropertyNamesContractResolver. Básicamente NamingStrategypara el primero influiría en los contratos generados por el segundo. Eso podría ser lo que estás viendo. En su lugar, pruebe la nueva recomendación y avíseme si soluciona su problema.
dbc
1
¿Existe un flexible NamingStrategy, de modo que sea capaz de analizar tanto el caso camel como el caso pascal?
Shimmy Weitzhandler
@dbc En el ejemplo de código inicial, ¿qué se configsupone que es?
Ryan Lundy
@RyanLundy - Me lo copió de la pregunta inicial, que mostró la siguiente línea de código: config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();. Parece ser la API web de MVC 4 HttpConfiguration, consulte ¿Cómo configurar JsonSerializerSettings personalizados para Json.NET en la API web de MVC 4? .
dbc
12

Esa es una muy buena respuesta. Pero, ¿por qué no anular el ResolveDictionaryKey?

class CamelCaseExceptDictionaryResolver : CamelCasePropertyNamesContractResolver
    {
        #region Overrides of DefaultContractResolver

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

        #endregion
    }
Dmitriy
fuente
Mucho conciso. Gracias por compartir.
Abu Abdullah
1

La respuesta seleccionada es perfecta, pero supongo que para cuando escribo esto, el solucionador de contratos debe cambiar a algo como esto porque DictionaryKeyResolver ya no existe :)

public class CamelCaseExceptDictionaryKeysResolver : CamelCasePropertyNamesContractResolver
    {
        protected override JsonDictionaryContract CreateDictionaryContract(Type objectType)
        {
            JsonDictionaryContract contract = base.CreateDictionaryContract(objectType);
            contract.PropertyNameResolver = propertyName => propertyName;
            return contract;
        }
    }
sep
fuente
5
En realidad, lo contrario es cierto. Debe estar utilizando una versión anterior de Json.Net. DictionaryKeyResolverse agregó en la versión 7.0.1 y PropertyNameResolverse marcó como obsoleto.
Brian Rogers