DevToolBoxFREE
BlogAdvertise

JSON to C# 클래스: System.Text.Json, Newtonsoft, Records 가이드

17분 읽기by DevToolBox

JSON을 C# 클래스로 변환하는 것은 현대 .NET 개발에서 필수적인 작업입니다. ASP.NET Core Web API, Unity 게임, Blazor 앱, MAUI 모바일 클라이언트 중 무엇을 구축하든 JSON 응답을 역직렬화하기 위해 강력한 타입의 C# 클래스가 필요합니다. 이 가이드는 타입 매핑, System.Text.Json, Newtonsoft.Json, record 타입, POCO 생성, 모범 사례를 다룹니다.

무료 온라인 JSON to C# 클래스 변환기를 사용해 보세요.

JSON to C# 클래스 변환이란?

JSON은 웹 API, 클라우드 서비스, 구성 시스템의 보편적인 데이터 교환 형식입니다. C#은 정적 타입 언어로 명시적 클래스 정의가 필요합니다. JSON to C# 클래스 변환은 JSON 문서를 분석하여 적절한 타입의 속성과 직렬화 특성을 가진 대응하는 C# 클래스를 생성합니다.

일반적인 ASP.NET Core 애플리케이션에서 컨트롤러는 JSON 문자열로 HTTP 요청 본문을 받습니다. 프레임워크는 System.Text.Json이나 Newtonsoft.Json을 사용하여 JSON을 C# 객체로 변환해야 합니다.

Unity, Blazor, Azure Functions에서도 동일한 변환이 필요합니다. 프로세스는 동일합니다: JSON 구조 검사, 타입 결정, 중첩과 배열 처리.

JSON to C#: 타입 매핑

JSON 타입이 C# 타입에 어떻게 매핑되는지 이해하는 것이 변환의 기초입니다:

JSON 타입예시C# 타입참고
string"hello"string항상 System.String
number(정수)42int, longnull 가능시 int?
number(소수)3.14double, decimal금융 데이터에는 decimal
booleantrueboolnull 가능시 bool?
nullnullnullNullable 타입
array[1,2]List<T>List 권장
object{"k":"v"}중첩 클래스강한 타입 클래스 권장

JSON에서 C# 클래스를 생성할 때 값 타입과 Nullable 타입의 선택이 중요합니다. 금액에는 항상 decimal을 사용하세요.

JSON to C# 변환 작동 원리

JSON to C# 클래스 변환기는 체계적인 프로세스를 따릅니다:

  1. JSON 구조 파싱: 구문 트리 구축.
  2. 속성 타입 추론: 각 속성의 C# 타입 결정.
  3. 이름 생성: PascalCase로 변환.
  4. 중첩 객체 처리: 각 중첩 객체가 별도 클래스 생성.
  5. 배열 처리: 요소 타입 분석.
  6. 특성 추가: [JsonPropertyName] 또는 [JsonProperty].
  7. 소스 코드 출력: 포맷된 C# 코드.

코드 예제: System.Text.Json과 Newtonsoft로 JSON to C#

System.Text.Json (.NET 8+): JsonSerializer와 특성

System.Text.Json은 .NET 내장 고성능 JSON 시리얼라이저입니다. .NET 8부터 AOT 컴파일을 위한 소스 제너레이터를 지원합니다:

// === Sample JSON ===
// {
//   "user_id": 1001,
//   "user_name": "Alice",
//   "email": "alice@example.com",
//   "is_active": true,
//   "balance": 1250.75,
//   "tags": ["admin", "developer"],
//   "address": {
//     "street": "123 Main St",
//     "city": "Springfield",
//     "zip_code": "62704"
//   }
// }

// === C# class with System.Text.Json attributes ===
using System.Text.Json;
using System.Text.Json.Serialization;

public class User
{
    [JsonPropertyName("user_id")]
    public long UserId { get; set; }

    [JsonPropertyName("user_name")]
    public string UserName { get; set; } = "";

    public string Email { get; set; } = "";

    [JsonPropertyName("is_active")]
    public bool IsActive { get; set; }

    public decimal Balance { get; set; }

    public List<string> Tags { get; set; } = new();

    public Address Address { get; set; } = new();
}

public class Address
{
    public string Street { get; set; } = "";
    public string City { get; set; } = "";

    [JsonPropertyName("zip_code")]
    public string ZipCode { get; set; } = "";
}

// === Deserialization with JsonSerializer ===
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

// Single object
User? user = JsonSerializer.Deserialize<User>(jsonString, options);

// List of objects
List<User>? users = JsonSerializer.Deserialize<List<User>>(
    jsonArrayString, options);

// === Custom JsonConverter for special cases ===
public class EpochToDateTimeConverter : JsonConverter<DateTime>
{
    public override DateTime Read(
        ref Utf8JsonReader reader, Type typeToConvert,
        JsonSerializerOptions options)
    {
        return DateTimeOffset.FromUnixTimeSeconds(
            reader.GetInt64()).DateTime;
    }

    public override void Write(
        Utf8JsonWriter writer, DateTime value,
        JsonSerializerOptions options)
    {
        writer.WriteNumberValue(
            new DateTimeOffset(value).ToUnixTimeSeconds());
    }
}

// Usage: [JsonConverter(typeof(EpochToDateTimeConverter))]
// public DateTime CreatedAt { get; set; }

// === .NET 8 Source Generator (AOT-friendly) ===
[JsonSerializable(typeof(User))]
[JsonSerializable(typeof(List<User>))]
public partial class AppJsonContext : JsonSerializerContext { }

// Zero-reflection deserialization:
User? u = JsonSerializer.Deserialize(
    jsonString, AppJsonContext.Default.User);

Newtonsoft.Json: JsonConvert와 특성

Newtonsoft.Json은 C# JSON 역직렬화의 사실상 표준 라이브러리입니다. JObject, LINQ-to-JSON, 커스텀 JsonConverter를 제공합니다:

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;

public class User
{
    [JsonProperty("user_id")]
    public long UserId { get; set; }

    [JsonProperty("user_name")]
    public string UserName { get; set; } = "";

    public string Email { get; set; } = "";

    [JsonProperty("is_active")]
    public bool IsActive { get; set; }

    public decimal Balance { get; set; }

    public List<string> Tags { get; set; } = new();

    public Address Address { get; set; } = new();
}

// === Deserialization with JsonConvert ===
var settings = new JsonSerializerSettings
{
    MissingMemberHandling = MissingMemberHandling.Ignore,
    NullValueHandling = NullValueHandling.Ignore,
    DateFormatString = "yyyy-MM-ddTHH:mm:ssZ"
};

// Single object
User? user = JsonConvert.DeserializeObject<User>(
    jsonString, settings);

// List of objects
List<User>? users = JsonConvert.DeserializeObject<List<User>>(
    jsonArrayString, settings);

// === Dynamic parsing with JObject ===
JObject obj = JObject.Parse(jsonString);
string? name = (string?)obj["user_name"];
JArray? tags = (JArray?)obj["tags"];
int tagCount = tags?.Count ?? 0;

// LINQ-to-JSON queries
var activeUsers = JArray.Parse(jsonArrayString)
    .Where(u => (bool)u["is_active"]!)
    .Select(u => (string?)u["user_name"])
    .ToList();

// === Custom JsonConverter ===
public class BoolToIntConverter : JsonConverter<bool>
{
    public override bool ReadJson(
        JsonReader reader, Type objectType, bool existingValue,
        bool hasExistingValue, JsonSerializer serializer)
    {
        return Convert.ToInt32(reader.Value) == 1;
    }

    public override void WriteJson(
        JsonWriter writer, bool value,
        JsonSerializer serializer)
    {
        writer.WriteValue(value ? 1 : 0);
    }
}

// Usage: [JsonConverter(typeof(BoolToIntConverter))]
// public bool IsActive { get; set; }

C# Records: 불변 데이터 모델

C# 9+ records는 불변 데이터 모델을 간결하게 정의합니다. Equals, GetHashCode, with 표현식을 자동 생성합니다:

// C# Record classes for JSON deserialization (C# 9+)
using System.Text.Json.Serialization;

public record User(
    [property: JsonPropertyName("user_id")] long UserId,
    [property: JsonPropertyName("user_name")] string UserName,
    string Email,
    [property: JsonPropertyName("is_active")] bool IsActive,
    decimal Balance,
    List<string> Tags,
    Address Address
);

public record Address(
    string Street,
    string City,
    [property: JsonPropertyName("zip_code")] string ZipCode
);

// Deserialization works seamlessly with records
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};
User? user = JsonSerializer.Deserialize<User>(json, options);

// Records are immutable: use "with" for modified copies
User updated = user! with { Email = "new@example.com" };

// Init-only record class (C# 10+)
public record class Product
{
    public required long Id { get; init; }
    public required string Name { get; init; }
    public required decimal Price { get; init; }
    public bool InStock { get; init; }
    public List<string> Categories { get; init; } = new();
}

// Value-based equality: two records with same data are equal
var p1 = new Product { Id = 1, Name = "Keyboard", Price = 79.99m };
var p2 = new Product { Id = 1, Name = "Keyboard", Price = 79.99m };
Console.WriteLine(p1 == p2);  // True

수동 POCO: 속성이 있는 클래스

완전한 제어가 필요할 때 전통적인 POCO와 INotifyPropertyChanged 또는 AutoMapper가 최대 유연성을 제공합니다:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization;

// POCO with INotifyPropertyChanged for WPF/MAUI data binding
public class Product : INotifyPropertyChanged
{
    private long _id;
    private string _name = "";
    private decimal _price;
    private bool _inStock;
    private List<string> _categories = new();

    [JsonPropertyName("product_id")]
    public long Id
    {
        get => _id;
        set => SetField(ref _id, value);
    }

    public string Name
    {
        get => _name;
        set => SetField(ref _name, value);
    }

    public decimal Price
    {
        get => _price;
        set => SetField(ref _price, value);
    }

    [JsonPropertyName("in_stock")]
    public bool InStock
    {
        get => _inStock;
        set => SetField(ref _inStock, value);
    }

    public List<string> Categories
    {
        get => _categories;
        set => SetField(ref _categories, value);
    }

    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler? PropertyChanged;

    private void OnPropertyChanged(
        [CallerMemberName] string? name = null)
    {
        PropertyChanged?.Invoke(
            this, new PropertyChangedEventArgs(name));
    }

    private bool SetField<T>(
        ref T field, T value,
        [CallerMemberName] string? name = null)
    {
        if (EqualityComparer<T>.Default.Equals(field, value))
            return false;
        field = value;
        OnPropertyChanged(name);
        return true;
    }

    // Override ToString for debugging
    public override string ToString()
        => $"Product {{ Id={Id}, Name={Name}, Price={Price} }}";
}

// Usage with AutoMapper (DTO to domain model):
// var config = new MapperConfiguration(cfg =>
//     cfg.CreateMap<ProductDto, Product>());
// var mapper = config.CreateMapper();
// Product product = mapper.Map<Product>(dto);

중첩 JSON 구조 작업

실제 API는 깊게 중첩된 객체와 다형적 타입을 포함합니다:

중첩 객체: 각 중첩 레벨이 별도의 C# 클래스를 생성합니다.

객체 배열: "items": [{"id": 1}]은 C#에서 List<Item>으로 매핑됩니다.

다형적 역직렬화: .NET 7+는 [JsonDerivedType][JsonPolymorphic]를 지원합니다.

// Polymorphic deserialization with .NET 7+ System.Text.Json
using System.Text.Json.Serialization;

[JsonPolymorphic(TypeDiscriminatorPropertyName = "type")]
[JsonDerivedType(typeof(EmailNotification), "email")]
[JsonDerivedType(typeof(SmsNotification), "sms")]
[JsonDerivedType(typeof(PushNotification), "push")]
public abstract class Notification
{
    public string Type { get; set; } = "";
    public string Message { get; set; } = "";
    public DateTime CreatedAt { get; set; }
}

public class EmailNotification : Notification
{
    public string Recipient { get; set; } = "";
    public string Subject { get; set; } = "";
}

public class SmsNotification : Notification
{
    public string PhoneNumber { get; set; } = "";
}

public class PushNotification : Notification
{
    public string DeviceToken { get; set; } = "";
    public string Title { get; set; } = "";
}

// JSON input:
// {"type":"email","message":"Hello","recipient":"a@b.com","subject":"Hi"}
// Automatically deserializes to EmailNotification

// Nested classes with arrays example
public class ApiResponse
{
    public bool Success { get; set; }
    public UserData Data { get; set; } = new();
    public List<ErrorDetail> Errors { get; set; } = new();
}

public class UserData
{
    public User User { get; set; } = new();
    public List<Order> Orders { get; set; } = new();
    public Address BillingAddress { get; set; } = new();
    public Address ShippingAddress { get; set; } = new();
}

public class Order
{
    public long OrderId { get; set; }
    public decimal Total { get; set; }
    public List<OrderItem> Items { get; set; } = new();
}

public class OrderItem
{
    public long ProductId { get; set; }
    public string Name { get; set; } = "";
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
}

고급 패턴: 소스 제너레이터, AOT, Nullable 참조 타입

.NET 8 System.Text.Json 소스 제너레이터는 컴파일 시간에 직렬화 코드를 생성하여 리플렉션을 제거하고 Native AOT 배포를 가능하게 합니다:

// .NET 8 Source Generator for AOT-friendly serialization
using System.Text.Json;
using System.Text.Json.Serialization;

public record Product(
    [property: JsonPropertyName("product_id")] long ProductId,
    string Name,
    decimal Price,
    [property: JsonPropertyName("in_stock")] bool InStock,
    List<string> Tags,
    [property: JsonPropertyName("created_at")] DateTime CreatedAt
);

// Source generator context
[JsonSerializable(typeof(Product))]
[JsonSerializable(typeof(List<Product>))]
[JsonSourceGenerationOptions(
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    GenerationMode = JsonSourceGenerationMode.Default)]
public partial class AppJsonContext : JsonSerializerContext { }

// Zero-reflection serialization (AOT-compatible)
var product = JsonSerializer.Deserialize(
    json, AppJsonContext.Default.Product);
var jsonOut = JsonSerializer.Serialize(
    product, AppJsonContext.Default.Product);

// Works with ASP.NET Core minimal APIs
var builder = WebApplication.CreateBuilder(args);
builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.TypeInfoResolverChain
        .Insert(0, AppJsonContext.Default);
});

C# 8+의 Nullable 참조 타입은 컴파일 시간 null 안전성을 제공합니다. C# 11+의 required 속성과 결합:

// Nullable reference types + required properties (C# 11+)
#nullable enable

public class UserProfile
{
    public required string Name { get; init; }
    public required string Email { get; init; }
    public string? Nickname { get; init; }     // optional
    public int Age { get; init; }
    public string? AvatarUrl { get; init; }     // optional
    public required List<string> Roles { get; init; }
}

// Deserialization enforces required properties
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
};
var user = JsonSerializer.Deserialize<UserProfile>(json, options)
    ?? throw new InvalidOperationException(
        "Failed to deserialize user profile");

// Compiler warning if you forget to set required properties:
// var profile = new UserProfile { Name = "Alice" };
// Error CS9035: Required member 'Email' must be set

// JsonRequired attribute for runtime enforcement
public class StrictModel
{
    [JsonRequired]
    public string Id { get; set; } = "";

    [JsonRequired]
    public string Name { get; set; } = "";

    public string? Description { get; set; }
}

System.Text.Json vs Newtonsoft: System.Text.Json은 2-5배 빠르고, 메모리 사용량이 적으며, 소스 제너레이터를 지원합니다. Newtonsoft는 더 많은 기능을 제공합니다.

JSON to C# 변환 모범 사례

견고한 .NET 애플리케이션 구축을 위한 모범 사례:

PascalCase 속성과 특성: [JsonPropertyName]으로 JSON 키 매핑.

Nullable 타입 활성화: #nullable enable으로 컴파일 시간 null 안전.

알 수 없는 속성 처리: System.Text.Json은 기본적으로 무시. Newtonsoft는 MissingMemberHandling.Ignore 설정.

금액에는 decimal: 금액에 float이나 double 사용 금지.

DTO에는 records 우선: init-only 속성의 records로 불변성.

성능을 위한 소스 제너레이터: .NET 8+에서 [JsonSerializable].

역직렬화 데이터 검증: 데이터 어노테이션 또는 FluentValidation.

관련 도구: JSON to Java, JSON to TypeScript, JSON to Kotlin.

JSON to JavaJSON to TypeScriptJSON to Kotlin

자주 묻는 질문

System.Text.Json과 Newtonsoft 중 어떤 것을 사용해야 하나요?

새로운 .NET 8+ 프로젝트에는 System.Text.Json이 권장됩니다. 프레임워크 내장이며, 2-5배 빠르고, AOT 소스 제너레이터를 지원합니다. Newtonsoft는 기존 프로젝트나 JObject가 필요한 복잡한 시나리오에 유용합니다.

C# Records와 전통적인 클래스 중 무엇을 사용해야 하나요?

불변 DTO에는 Records(C# 9+)를 사용하세요. 가변성이나 INotifyPropertyChanged가 필요하면 클래스. ASP.NET Core에서는 Records가 모던한 모범 사례입니다.

알 수 없는 JSON 속성을 어떻게 처리하나요?

System.Text.Json은 기본적으로 무시합니다. 캡처하려면 [JsonExtensionData]를 사용하세요. Newtonsoft는 기본적으로 예외를 던지므로 MissingMemberHandling.Ignore를 설정하세요.

JSON에서 C# 클래스로의 변환은 모든 .NET 개발자의 기본 기술입니다. 무료 온라인 도구로 즉시 코드를 생성하세요.

무료 온라인 도구로 JSON을 C# 클래스로 즉시 변환하세요.

도움이 되었나요?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Partner Picks

Sponsor this article

Place your product next to this developer topic with tracked clicks.

Ask about article sponsorship

This site uses cookies for analytics and to display ads. By continuing to browse, you agree. Privacy Policy