Как стать автором
Обновить

Как изменить формат данных JSON на Snake Case в ASP.NET Core Web API

Время на прочтение4 мин
Количество просмотров6.4K

Стандартный способ отображения данных в ASP.NET Web API - это Camel Case. Но иногда возникают задачи, когда нужно изменить формат данных на нечто другое. Например, на фронтенде у вас может быть SPA, которое как раз работает с данными в формате snake case. В этой статье я покажу, как изменить формат сериализации в ASP.NET Core Web API.

Camel case vs Snake case
Camel case vs Snake case

В статье приведены примеры кода, которые необходимо будет перенести в свой проект. В конце поста - ссылка на Github репозиторий, где я уже настроил приложение на сериализацию в snake case. Все примеры кода и проект в репозитории написаны на ASP.NET Core версии .net5.

Меняем формат сериализации запросов и ответов сервера

Все, что нам нужно сделать для изменения сериализации, это установить Naming Policy в настройках приложения. Стандартный полиси - это Camel Case. Установка полиси на Snake Case - задача несложная.

Сначала добавим утилитарные методы для трансформации строк к snake case:

using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Utils.Helpers;

namespace YourNamespace
{
    public static class JsonSerializationExtensions
    {
        private static readonly SnakeCaseNamingStrategy _snakeCaseNamingStrategy
            = new SnakeCaseNamingStrategy();

        private static readonly JsonSerializerSettings _snakeCaseSettings = new JsonSerializerSettings
        {
            ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = _snakeCaseNamingStrategy
            }
        };

        public static string ToSnakeCase(this T instance)
        {
            if (instance == null)
              {
                   throw new ArgumentNullException(paramName: nameof(instance));
               }

            return JsonConvert.SerializeObject(instance, _snakeCaseSettings);
        }

        public static string ToSnakeCase(this string @string)
        {
            if (@string == null)
              {
                   throw new ArgumentNullException(paramName: nameof(@string));
               }

            return _snakeCaseNamingStrategy.GetPropertyName(@string, false);
        }
    }
}

Здесь мы добавляем пару полезных методов: первая перегрузка метода нам пригодится для применения на объектах, вторая - для строкового значения. Мы используем тут класс SnakeCaseNamingStrategy для трансформации строк. Эти методы понадобятся нам в реализации нашего Naming Policy: 

using System.Text.Json;
using Utils.Serialization;

namespace YourNamespace
{
    public class SnakeCaseNamingPolicy : JsonNamingPolicy
    {
        public override string ConvertName(string name) => name.ToSnakeCase();
    }
}

Здесь мы как раз используем метод-экстеншн ToSnakeCase() для трансформации. Инстанс класса SnakeCaseNamingPolicy мы будем использовать в Startup.cs в методе ConfigureServices:

public class Startup
{
  public void ConfigureServices(IServiceCollection services)
  {
    // ...
    services
        .AddControllers()
        .AddJsonOptions(x =>
        {
            x.JsonSerializerOptions.PropertyNamingPolicy = new SnakeCaseNamingPolicy();
        });
    // ...
  }
}

После добавления этой настройки наше приложение принимает и отдает данные в JSON в формате Snake Case:

Данные в Snake Case формате
Данные в Snake Case формате

Но когда у нас возникнет ошибка валидация входящих данных, мы обнаружим, что…

Формат выдачи ошибок валидации до сих пор в Camel Case
Формат выдачи ошибок валидации до сих пор в Camel Case

Скрин выше - это то, как будет отображена ошибка валидации. Как мы видим, она до сих пор в формате Camel Case, даже с примененными нами настройками. Более того, поля класса FirstName и LastName написаны уже в формате Pascal Case, а мы принимаем и отдаем их в Snake Case. Нам такое поведение не подходит, будем исправлять.

Меняем формат выдачи ошибок валидации

Чтобы это сделать, нам нужно заменить стандартную "фабрику ответов" в ASP на свою собственную. Сначала создадим класс, который и будет формировать нам структуру ошибок:

using System;
using System.Collections.Generic;
using System.Net;
using Microsoft.AspNetCore.Mvc;

namespace YourNamespace
{
    public class ValidationProblemDetails : ProblemDetails
    {
        // 400 status ccode is usually used for input validation errors
        public const int ValidationStatusCode = (int)HttpStatusCode.BadRequest;

        public ValidationProblemDetails(ICollection validationErrors)
        {
            ValidationErrors = validationErrors;
            Status = ValidationStatusCode;
            Title = "Request Validation Error";
        }

        public ICollection ValidationErrors { get; }

        public string RequestId => Guid.NewGuid().ToString();
    }
}

Этот класс принимает список ошибок, который и будет отображен в JSON. Класс наследуется от ProblemDetails из неймспейса Microsoft.AspNetCore.Mvc. Поле RequestId нам поможет найти конкретную запись ошибки в логах при просмотре через наш UI мониторинга.

Затем нам нужно создать класс для замены стандартного InvalidModelStateResponseFactory:

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Utils.Serialization;

namespace YourNamespace
{
    public class ValidationProblemDetailsResult : IActionResult
    {
        public async Task ExecuteResultAsync(ActionContext context)
        {
            var modelStateEntries = context.ModelState
                .Where(e => e.Value.Errors.Count > 0)
                .ToArray();

            var errors = new List();

            if (modelStateEntries.Any())
            {
                foreach (var (key, value) in modelStateEntries)
                {
                    errors.AddRange(value.Errors
                        .Select(modelStateError => new ValidationError(
                            name: key.ToSnakeCase(),
                            description: modelStateError.ErrorMessage)));
                }
            }

            await new JsonErrorResponse(
                context: context.HttpContext,
                error: new ValidationProblemDetails(errors),
                statusCode: ValidationProblemDetails.ValidationStatusCode).WriteAsync();
        }
    }
}

Напоследок добавим замену механизма формирования ошибок в классе Startup.cs:

public class Startup
{
   // ...
  public void ConfigureServices(IServiceCollection services)
  {
    // ...
    services
        .Configure(x =>
        {
            x.InvalidModelStateResponseFactory = ctx => new ValidationProblemDetailsResult();
        });
    // ...
  }
}

И теперь наши ошибки сериализуются в Snake Case тоже:

Структура ошибки в формате Snake Case
Структура ошибки в формате Snake Case

После всех изменений наше приложение теперь не только отдает и принимает JSON данные в формате Snake Case, но и показывает валидационные ошибки тоже в том виде, в котором нам нужно. Здесь по ссылке вы можете открыть Github репозиторий, где есть пример настроенного приложения. По описанным шагам вы можете применить не только Snake Case, но и любой другой формат сериализации данных, который вам по душе.

Теги:
Хабы:
Всего голосов 10: ↑7 и ↓3+4
Комментарии29

Публикации

Истории

Работа

.NET разработчик
68 вакансий

Ближайшие события