Web API 2: cómo devolver JSON con nombres de propiedad camelCased, en objetos y sus subobjetos

104

ACTUALIZAR

Gracias por todas las respuestas. Estoy en un nuevo proyecto y parece que finalmente llegué al fondo de esto: parece que el siguiente código fue de hecho el culpable:

public static HttpResponseMessage GetHttpSuccessResponse(object response, HttpStatusCode code = HttpStatusCode.OK)
{
    return new HttpResponseMessage()
    {
        StatusCode = code,
        Content = response != null ? new JsonContent(response) : null
    };
}

en otra parte...

public JsonContent(object obj)
{
    var encoded = JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.None, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore } );
    _value = JObject.Parse(encoded);

    Headers.ContentType = new MediaTypeHeaderValue("application/json");
}

Había pasado por alto el JsonContent de aspecto inocuo asumiendo que era WebAPI, pero no.

Esto se usa en todas partes ... ¿Puedo ser el primero en decir, wtf? O tal vez debería ser "¿Por qué están haciendo esto?"


sigue la pregunta original

Uno hubiera pensado que esto sería una configuración de configuración simple, pero se me ha eludido durante demasiado tiempo.

He analizado varias soluciones y respuestas:

https://gist.github.com/rdingwall/2012642

no parece aplicarse a la última versión de WebAPI ...

Lo siguiente no parece funcionar: los nombres de las propiedades siguen siendo PascalCased.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;

json.UseDataContractJsonSerializer = true;
json.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;

json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

La respuesta de Mayank aquí: Los subobjetos CamelCase JSON WebAPI (objetos anidados, objetos secundarios) parecían una respuesta insatisfactoria pero viable hasta que me di cuenta de que estos atributos tendrían que agregarse al código generado ya que estamos usando linq2sql ...

¿Alguna forma de hacer esto automáticamente? Este 'desagradable' me ha atormentado durante mucho tiempo.

Tom
fuente
También hay una razón por la que Linq2SQL produce clases parciales. Además ... ¡¿Linq2SQL WTF ?!
Aron
1
Gracias, pero este enlace es para MVC, estoy usando Web API 2, y no estoy seguro de si hay una manera de establecer el tipo de contenido de esta manera y devolver una cadena, pero si la hay, no parece como la solución correcta .. Gracias por el consejo sobre las clases parciales también, pero ¿es posible agregar un atributo a una propiedad definida en la otra parte del parcial?
Tom
También sí, linq2sql wtf ... no es mi decisión :)
Tom
el resultado es el mismo, la única diferencia es dónde inyecta el JsonSerializer. stackoverflow.com/questions/13274625/…
Aron

Respuestas:

175

Poniéndolo todo junto, obtienes ...

protected void Application_Start()
{
    HttpConfiguration config = GlobalConfiguration.Configuration;
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
}
Aron
fuente
Definitivamente la forma de activarlo, pero mi problema era que se ignoraba esta configuración (ver mi respuesta)
Tom
1
@ Tom erm ... Tom, ¿sabías lo que json.UseDataContractJsonSerializer = true;hace? Le dice a WebAPI que no se utilice Json.Netpara la serialización. > _ <
Aron
Sí, lo hago ahora. Sin embargo, también hubo un problema adicional. Verifiqué esto. Mira mi respuesta. También vea stackoverflow.com/questions/28552567/…
Tom
1
De hecho, en una inspección más cercana, resulta que estaba equivocado en mi conclusión anterior. Ver mi actualización.
Tom
28

Esto es lo que funcionó para mí:

internal static class ViewHelpers
{
    public static JsonSerializerSettings CamelCase
    {
        get
        {
            return new JsonSerializerSettings {
                ContractResolver = new CamelCasePropertyNamesContractResolver()
            };
        }
    }
}

Y entonces:

[HttpGet]
[Route("api/campaign/list")]
public IHttpActionResult ListExistingCampaigns()
{
    var domainResults = _campaignService.ListExistingCampaigns();
    return Json(domainResults, ViewHelpers.CamelCase);
}

La clase CamelCasePropertyNamesContractResolverproviene de Newtonsoft.Json.dllen Json.NET biblioteca.

felix-b
fuente
3
Este enfoque es muy útil cuando se quiere tener camelCasing solo para algunas API, no para todas las API de la aplicación. (Y)
droidebot
15

Resulta que

return Json(result);

fue el culpable, lo que provocó que el proceso de serialización ignorara la configuración de camelcase. Y eso

return Request.CreateResponse(HttpStatusCode.OK, result, Request.GetConfiguration());

era el droide que estaba buscando.

también

json.UseDataContractJsonSerializer = true;

Estaba poniendo una llave inglesa en las obras y resultó NO ser el droide que estaba buscando.

Tom
fuente
En realidad, esta es la respuesta incorrecta. Vea mi actualización en la pregunta.
Tom
De hecho, descubrí que este es el caso. Al regresar Json(result), estaba viendo todo en PascalCase, pero cuando regresé Content(StatusCode, result)funcionó como se esperaba.
DeeKayy90
12

Todas las respuestas anteriores no me funcionaron con Owin Hosting y Ninject. Esto es lo que funcionó para mí:

public partial class Startup
{
    public void Configuration(IAppBuilder app)
    {
        // Get the ninject kernel from our IoC.
        var kernel = IoC.GetKernel();

        var config = new HttpConfiguration();

        // More config settings and OWIN middleware goes here.

        // Configure camel case json results.
        ConfigureCamelCase(config);

        // Use ninject middleware.
        app.UseNinjectMiddleware(() => kernel);

        // Use ninject web api.
        app.UseNinjectWebApi(config);
    }

    /// <summary>
    /// Configure all JSON responses to have camel case property names.
    /// </summary>
    private void ConfigureCamelCase(HttpConfiguration config)
    {
        var jsonFormatter = config.Formatters.JsonFormatter;
        // This next line is not required for it to work, but here for completeness - ignore data contracts.
        jsonFormatter.UseDataContractJsonSerializer = false;
        var settings = jsonFormatter.SerializerSettings;
#if DEBUG
        // Pretty json for developers.
        settings.Formatting = Formatting.Indented;
#else
        settings.Formatting = Formatting.None;
#endif
        settings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    }
}

La diferencia clave es: new HttpConfiguration () en lugar de GlobalConfiguration.Configuration.

mkaj
fuente
Para autohospedaje a través de OWIN, esto es perfecto. ¡Gracias!
Julian Melville
3
Si está usando Owin, esta solución funciona perfectamente, ¡pero solo después de que se haya arrancado todo el cabello!
Alastair
10

Código de WebApiConfig:

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            //This line sets json serializer's ContractResolver to CamelCasePropertyNamesContractResolver, 
            //  so API will return json using camel case
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();

        }
    }


Asegúrese de que su método de acción de API devuelva datos de la siguiente manera y que haya instalado la última versión de Json.Net/Newtonsoft.Json instalada:

    [HttpGet]
    public HttpResponseMessage List()
    {
        try
        {
            var result = /*write code to fetch your result*/;
            return Request.CreateResponse(HttpStatusCode.OK, cruises);
        }
        catch (Exception ex)
        {
            return Request.CreateResponse(HttpStatusCode.InternalServerError, ex.Message);
        }
    }
Jay Shah
fuente
4

En su Owin Startup agregue esta línea ...

 public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        var webApiConfiguration = ConfigureWebApi();            
        app.UseWebApi(webApiConfiguration);
    }

    private HttpConfiguration ConfigureWebApi()
    {
        var config = new HttpConfiguration();

        // ADD THIS LINE HERE AND DONE
        config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

        config.MapHttpAttributeRoutes();
        return config;
    }
}
smatthews1999
fuente
3

Aquí hay uno oscuro, cuando el atributo de ruta no coincide con la URL GET pero la URL GET coincide con el nombre del método, la directiva jsonserializer camel case se ignoraría, por ejemplo

http: // sitio web / api / geo / geodata

//uppercase fail cakes
[HttpGet]
[Route("countries")]
public async Task<GeoData> GeoData()
{
    return await geoService.GetGeoData();
}

//lowercase nomnomnom cakes
[HttpGet]
[Route("geodata")]
public async Task<GeoData> GeoData()
{
    return await geoService.GetGeoData();
}
evento-botón-jenson
fuente
2

Lo he resuelto de la siguiente manera.

[AllowAnonymous]
[HttpGet()]
public HttpResponseMessage GetAllItems(int moduleId)
{
    HttpConfiguration config = new HttpConfiguration();
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
            config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;

            try
            {
                List<ItemInfo> itemList = GetItemsFromDatabase(moduleId);
                return Request.CreateResponse(HttpStatusCode.OK, itemList, config);
            }
            catch (System.Exception ex)
            {
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, ex.Message);
            }
}
Khademul Basher
fuente
0

Estoy usando WebApi con Breeze y encontré el mismo problema cuando intenté ejecutar una acción que no es de brisa en un controlador de brisa. Traté de usar el apprach Request.GetConfiguration pero el mismo resultado. Entonces, cuando accedo al objeto devuelto por Request.GetConfiguration, me doy cuenta de que el serializador usado por solicitud es el que usa el servidor de brisa para hacer su magia. De cualquier forma, resolví mi problema creando una HttpConfiguration diferente:

public static HttpConfiguration BreezeControllerCamelCase
        {
            get
            {
                var config = new HttpConfiguration();
                var jsonSerializerSettings = config.Formatters.JsonFormatter.SerializerSettings;
                jsonSerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                jsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
                config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;

                return config;
            }
        }

y pasándolo como parámetro en Request.CreateResponse de la siguiente manera:

return this.Request.CreateResponse(HttpStatusCode.OK, result, WebApiHelper.BreezeControllerCamelCase);
Leonardo Neninger
fuente