Web API 2: como retornar JSON com nomes de propriedade camelCased, em objetos e seus subobjetos

104

ATUALIZAR

Obrigado por todas as respostas. Estou em um novo projeto e parece que finalmente cheguei ao fundo disso: parece que o código a seguir era de fato o culpado:

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

em outro lugar...

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

Eu tinha esquecido o JsonContent de aparência inócua presumindo que fosse WebAPI, mas não.

Isso é usado em todos os lugares ... Posso ser o primeiro a dizer, wtf? Ou talvez devesse ser "Por que eles estão fazendo isso?"


segue a pergunta original

Alguém poderia pensar que essa seria uma configuração simples, mas ela me iludiu por muito tempo.

Eu tenho olhado para várias soluções e respostas:

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

não parece se aplicar à versão mais recente da WebAPI ...

O seguinte parece não funcionar - os nomes das propriedades ainda são PascalCased.

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;

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

json.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); 

A resposta de Mayank aqui: CamelCase JSON WebAPI Sub-objetos (objetos aninhados, objetos filhos) parecia uma resposta insatisfatória, mas viável, até que percebi que esses atributos teriam que ser adicionados ao código gerado, pois estamos usando linq2sql ...

Alguma maneira de fazer isso automaticamente? Este 'desagradável' tem me atormentado por um longo tempo.

Tom
fonte
Também há uma razão pela qual Linq2SQL produz classes parciais. Além disso ... Linq2SQL WTF ?!
Aron
1
Obrigado, mas este link é para MVC, é Web API 2 que estou usando e não tenho certeza se existe uma maneira de definir o tipo de conteúdo como este e retornar uma string, mas se houver, não parece como a solução certa .. Obrigado pela dica sobre classes parciais também, mas é possível adicionar um atributo a uma propriedade definida na outra parte da parcial?
Tom
Além disso, sim, linq2sql wtf ... não é minha decisão :)
Tom
o resultado é o mesmo, a única diferença é onde você injeta o JsonSerializer. stackoverflow.com/questions/13274625/…
Aron

Respostas:

175

Juntando tudo, você obtém ...

protected void Application_Start()
{
    HttpConfiguration config = GlobalConfiguration.Configuration;
    config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
    config.Formatters.JsonFormatter.UseDataContractJsonSerializer = false;
}
Aron
fonte
Definitivamente a maneira de ativá-lo, mas meu problema era que essa configuração estava sendo ignorada (ver minha resposta)
Tom
1
@Tom erm ... Tom, você sabia o que json.UseDataContractJsonSerializer = true;faz? Diz ao WebAPI para não usar Json.Netpara serialização. > _ <
Aron
Sim, agora. No entanto, havia também um problema adicional. Eu verifiquei isso. Veja minha resposta. Consulte também stackoverflow.com/questions/28552567/…
Tom
1
Na verdade, em uma inspeção mais detalhada, descobri que eu estava errado em minha conclusão anterior. Veja minha atualização.
Tom
28

Isto é o que funcionou para mim:

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

E depois:

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

A classe CamelCasePropertyNamesContractResolvervem Newtonsoft.Json.dllem Json.NET biblioteca.

felix-b
fonte
3
Essa abordagem é muito útil quando se deseja ter o camelCasing apenas para algumas APIs, não para todas as APIs do aplicativo. (Y)
droidbot
15

Acontece que

return Json(result);

foi o culpado, fazendo com que o processo de serialização ignorasse a configuração do caso de camelo. E essa

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

era o andróide que eu procurava.

Além disso

json.UseDataContractJsonSerializer = true;

Estava colocando uma chave inglesa nas obras e descobri que NÃO era o andróide que eu procurava.

Tom
fonte
Esta é realmente a resposta errada. Veja minha atualização na pergunta.
Tom
Na verdade, achei que fosse esse o caso. Ao voltar Json(result)estava vendo tudo em PascalCase, mas quando voltei Content(StatusCode, result)funcionava como o esperado.
DeeKayy90
12

Todas as respostas acima não funcionaram para mim com Owin Hosting e Ninject. Aqui está o que funcionou para mim:

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

A principal diferença é: new HttpConfiguration () em vez de GlobalConfiguration.Configuration.

mkaj
fonte
Para auto-hospedagem via OWIN, isso é perfeito. Obrigado!
Julian Melville,
3
Se você estiver usando Owin, esta solução funciona perfeitamente, mas só depois de arrancar todo o seu cabelo!
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();

        }
    }


Certifique-se de que o método de ação da API retorne dados da seguinte maneira e de que você tenha instalado a versão mais recente do Json.Net/Newtonsoft.Json:

    [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
fonte
4

Em seu Owin Startup, adicione esta linha ...

 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
fonte
3

Aqui está um obscuro, quando o atributo de rota não correspondia ao URL GET, mas o url GET correspondia ao nome do método, a diretiva jsonserializer camel case seria ignorada, por exemplo

http: // site / 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ão-jenson
fonte
2

Eu resolvi isso das seguintes maneiras.

[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
fonte
0

Estou usando o WebApi com o Breeze e corri o mesmo problema ao tentar executar uma ação não brisa em um controlador brisa. Tentei usar o Apprach Request.GetConfiguration mas o mesmo resultado. Então, quando eu acesso o objeto retornado por Request.GetConfiguration eu percebo que o serializador usado por request é aquele que o servidor do breeze usa para fazer sua mágica. De qualquer forma, resolvi meu problema criando um 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;
            }
        }

e passando-o como parâmetro em Request.CreateResponse da seguinte forma:

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