본문 바로가기
프로그래밍/C#

C# 실무에서 사용하는 전역 JSON 옵션 설정

by bantomak 2025. 4. 28.
반응형

실무에서 전역 JSON 옵션 설정하기

C# 프로젝트를 진행하면서 JSON을 사용한다면 JSON 옵션 설정이 파편화되어있어서 여기저기서 다르게 Serilaize/Deserialize 돼서 괴로웠던 경험들이 한 번쯤은 있을 것이다. 이제 이렇게 파편화된 설정들이 프로젝트 초기에는 괜찮지만 코드량이 늘어나고 복잡도가 올라가면 컨트롤하기 힘들어지는 순간이 온다. 이때가 바로 전역으로 JSON 옵션을 설정해야 하는 시기이다.

Program.cs에서 AddJsonOption() 설정

AddJsonOption() 매서드를 통해서 옵션을 설정하면 전역적으로 해당 옵션이 적용된다.

  • Controller에서 기본 응답
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers()
    .AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
        options.JsonSerializerOptions.WriteIndented = true;
        options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
        options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
    });

var app = builder.Build();
app.MapControllers();
app.Run();

Controller에서 JSON 옵션 적용

Controller에서는 JSON옵션을 별도로 호출하지 않아도 자동으로 JSON 옵션이 적용된다.

  • return Ok(obj), return Json(obj)는 자동으로 적용

물론 직접 JsonSerializer.Serialize를 직접 호출하는 경우에는 명시적으로 옵션을 넘겨줘야 한다.

[HttpGet]
public IActionResult Get()
{
    var obj = new { UserName = "Alice", Status = "Active" };

    // 여기서는 그냥 Ok(obj) 만 해도
    // 내부 OutputFormatter가 자동으로 JsonOptions를 사용해서 직렬화해준다.
    return Ok(obj);
}

Service에서 JSON 옵션 전용

보통 Service 예제에서는 직접 JsonSerializer를 호출하는 경우가 많기 때문에 IOptions<JsonOptions>로 option을 받아와서 사용해야한다. Program.cs에서 AddJsonOptions()로 설정하면 IOptions<JsonOptions>로 DI를 사용해서 받아오는 게 가능하다.

이는 내부적으로 약속된 내용 인다. AddJsonOptions()로 설정하면 IOptions<JsonOptions>가 등록된다. 그래서 나중에 DI로 IOptions 를 주입받을 수 있는 것이다.

  • 이건 약속(Convention)이자, ASP.NET Core의 표준 동작 규칙
  • AddControllers().AddJsonOptions(options => {...})를 호출
  • ASP.NET Core는 내부적으로 services.Configure<JsonOptions>(options => {...})를 실행
  • Configure<TOptions>()는 자동으로 IOptions<TOptions>, IOptionsSnapshot<TOptions>, IOptionsMonitor<TOptions>같은 DI  컨테이너에 등록한다.  

결국 service.Configure<JsonOptions>()가 호출되었기 때문에 IOptions<JsonOptions>를 어디서나 주입받을 수 있는 것이다.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using System.Text.Json;

public class JsonHelperService
{
    private readonly JsonSerializerOptions _jsonOptions;

    public JsonHelperService(IOptions<JsonOptions> options)
    {
        // JsonOptions는 MVC 옵션이니까, 내부의 JsonSerializerOptions를 꺼낸다
        _jsonOptions = options.Value.JsonSerializerOptions;
    }

    public string Serialize<T>(T value)
    {
        return JsonSerializer.Serialize(value, _jsonOptions);
    }

    public T? Deserialize<T>(string json)
    {
        return JsonSerializer.Deserialize<T>(json, _jsonOptions);
    }
}

JsonSerializerOptions와 JsonOptions의 관계에 대해서

코드 작성 중에 드는 의문점은 JsonSerializerOptions을 AddJsonOptions에 추가했는데 Service에서는 IOption<JsonOption>으로 받아와서 사용하고 있다는 것이다. 두 클래스의 관계는 무엇일까?

  • JsonOptions은 JsonSerializerOptions를 포장한 껍데기(wrapper) 클래스이다.
  • JsonOptions는 ASP.NET Core MVC 가 쓰는 컨트롤러 레벨 JSON 직렬화 설정용 클래스이다.
  • 그 안에 진짜 System.Text.Json.JsonSerilaizerOptions가 포함되어 있다.
namespace Microsoft.AspNetCore.Mvc
{
    public class JsonOptions
    {
        public JsonSerializerOptions JsonSerializerOptions { get; } = new JsonSerializerOptions();
    }
}
  • JsonOptions : ASP.NET Core MVC 전용 설정 클래스 (컨트롤러 응답용)
  • JsonSerilaizerOptions : 실제 System.Text.Json의 직렬화 설정 클래스 (로우 레벨)

이렇게 설계된 이유에 대해서 알아보자면,

  • DI로 일관된 방식을 제공 : 모든 MVP 관련 설정은 IOptions<T> 패턴을 통해서 관리하려고 설계
    IOptions<MvcOptions>, IOptions<ApiBehaviorOptions>, IOptions<JsonOptions> 모두 같은 패턴
  • 확장성과 명시성 확보 : JsonOptions는 MVC 전용이라는 걸 명시적으로 구분해 줄 수 있음
    (System.Text.Json은 그냥 순수 JSON 라이브러리일 뿐, ASP.NET Core MVC에 종속적이지 않음.)

전체 흐름 정리

  • Progrma.cs - AddJsonOptions - 설정 > JsonSerilaizerOptions 옵션을 등록
  • AddJsonOptions 내부에 services.Configure<JsonOptions>(options => {...}) 실행 > DI로 등록
  • [IOptions<JsonOptions>] > DI로 가져와서 사용이 가능
  • [JsonSerializer] > 실제 Serialize/Deserialize 호출할 때 JsonSerializerOptions 사용

정리하자면

AddJsonOption()으로 설정하면 JSON 옵션이 전역적으로 적용된다. 하지만 JsonSerilaizer.Serialize()와 같이 직접 호출해서 사용하는 경우에는 DI를 사용해서 IOptions<JsonOptions>로 전역적으로 등록한 Json옵션 정보를 가져와서 등록해줘야한다. IOptions<JsonOptions>로 등록된 이유는 AddJsonOption() 매서드 내부에서 services.Configure<JsonOptions>(options => {...})로 자동 등록해주기 때문이다.

댓글