DevToolBox免费
博客

JSON转C#类:System.Text.Json、Newtonsoft和Records完整指南

17分钟阅读作者 DevToolBox

JSON 转换为 C# 类是现代 .NET 开发中的必备技能。无论你是构建 ASP.NET Core Web API、Unity 游戏、Blazor WebAssembly 应用还是 MAUI 移动客户端,都需要强类型 C# 类来反序列化 JSON 响应。本指南全面介绍类型映射、System.Text.Json 和 Newtonsoft.Json 策略、record 类型、POCO 生成、嵌套结构以及从 JSON 生成 C# 类的最佳实践。

立即试用我们的免费在线 JSON 转 C# 类转换工具。

什么是 JSON 转 C# 类转换?

JSON 是 Web API、云服务和配置系统的通用数据交换格式。C# 是静态类型语言,需要在程序使用数据前定义显式的类。JSON 转 C# 类转换通过分析 JSON 文档并生成具有正确类型属性、可空注解和序列化特性的对应 C# 类来弥合这一差距。

在典型的 ASP.NET Core 应用中,控制器接收 JSON 字符串格式的 HTTP 请求体。框架必须使用内置的 System.Text.Json 或 Newtonsoft.Json 库将 JSON 转换为 C# 对象。没有正确定义的类,C# JSON 反序列化操作会在运行时失败。JSON 转 C# 类转换器自动创建这些数据传输对象。

在 Unity 游戏开发中 JSON 配置定义关卡和物品数据,在 Blazor 应用中消费第三方 API,以及在 Azure Functions 中处理事件载荷时,同样需要这种转换。无论称之为 JSON 转 C# 类、JSON 转 POCO 还是从 JSON 生成 C# 类,底层过程都相同。

JSON 到 C#:类型映射

理解 JSON 类型如何映射到 C# 类型是任何 JSON 转 C# 转换的基础:

JSON 类型示例C# 类型说明
string"hello"string始终映射为 System.String
number(整数)42intlong超过 2^31-1 时使用 long;可空时使用 int?
number(小数)3.14doubledecimal金融数据使用 decimal
booleantruebool可空时使用 bool?
nullnullnull可空引用类型 string? 或可空值类型 int?
array[1, 2, 3]List<T>T[]推荐使用 List<T>
object{"k": "v"}嵌套类或 Dictionary优先使用强类型嵌套类

在从 JSON 生成 C# 类时,选择值类型(intbool)还是可空类型(int?bool?)很重要。启用可空引用类型后,JSON 中可能缺失的属性应声明为可空。货币值始终使用 decimal 而非 double

JSON 转 C# 转换的工作原理

JSON 转 C# 类转换器遵循系统化流程将原始 JSON 转换为可编译的 C# 源代码:

  1. 解析 JSON 结构:转换器对输入 JSON 进行词法分析,构建对象、数组和基本类型的抽象语法树。
  2. 推断属性类型:对每个键值对确定 C# 类型。
  3. 生成类名和属性名:将 JSON 键名转换为 PascalCase 属性名。
  4. 处理嵌套对象:每个嵌套 JSON 对象生成一个单独的 C# 类。
  5. 处理数组:分析数组元素确定元素类型。
  6. 添加序列化特性:当 JSON 键不符合 C# 命名规范时添加 [JsonPropertyName][JsonProperty]
  7. 输出源代码:生成格式化的 C# 源代码。

代码示例:使用 System.Text.Json 和 Newtonsoft 实现 JSON 转 C#

System.Text.Json(.NET 8+):JsonSerializer 和特性

System.Text.Json 是 .NET 内置的高性能 JSON 序列化器。从 .NET 8 开始支持源生成器和 AOT 编译。以下是使用 JsonSerializer[JsonPropertyName] 和自定义转换器的完整示例:

// === 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+ 的 record 提供了简洁的方式定义不可变数据模型。Records 自动生成 EqualsGetHashCodeToString,并支持 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:带属性的类

当需要完全控制 JSON 转 C# 类时,传统 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 很少返回扁平 JSON。大多数响应包含深度嵌套对象、对象数组和多态类型。将这些复杂结构转换为 C# 类需要仔细设计:

深度嵌套对象:每层嵌套生成一个单独的 C# 类。可以使用嵌套类、独立文件或在同一文件中定义的 records。

对象数组"items": [{"id": 1}] 映射为 List<Item>。不可变集合使用 IReadOnlyList<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 和可空引用类型

.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+ 的可空引用类型为 JSON 转 C# 类定义提供编译时空安全。配合 C# 11+ 的 required 属性,可以强制要求必需的 JSON 字段始终存在:

// 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.Json:System.Text.Json 在大多数工作负载上快 2-5 倍,内存占用更低,支持源生成器。Newtonsoft 提供更丰富的功能。新 .NET 8+ 项目推荐 System.Text.Json。

JSON 转 C# 转换最佳实践

将 JSON 转换为 C# 类时,遵循以下最佳实践构建健壮的 .NET 应用:

使用 PascalCase 属性配合序列化特性:C# 属性使用 PascalCase,用 [JsonPropertyName] 映射 JSON 键。

启用可空引用类型:添加 #nullable enable,将可选 JSON 属性标记为可空。

优雅处理未知属性:System.Text.Json 默认忽略未知属性。Newtonsoft 默认抛出异常,需配置 MissingMemberHandling.Ignore

货币值使用 decimal:绝不使用 floatdouble 表示金额。

DTO 优先使用 records:使用 C# records 配合 init-only 属性定义数据传输对象。

使用源生成器提升性能:在 .NET 8+ 项目中使用 [JsonSerializable] 消除反射开销。

验证反序列化数据:应用数据注解或 FluentValidation 规则验证反序列化对象。

相关工具推荐:JSON 转 Java 用于 Spring Boot 开发,JSON 转 TypeScript 用于前端接口,JSON 转 Kotlin 用于 Kotlin 项目。

JSON to JavaJSON to TypeScriptJSON to Kotlin

常见问题

System.Text.Json 和 Newtonsoft.Json 哪个更适合 JSON 转 C#?

新 .NET 8+ 项目推荐 System.Text.Json。它内置于框架中,性能提升 2-5 倍,内存占用更低,支持 AOT 源生成器。Newtonsoft 适用于遗留项目和需要 JObject 动态解析的复杂场景。两者都支持 records、自定义转换器和可空引用类型。

应该使用 C# records 还是传统类进行 JSON 反序列化?

需要不可变 DTO 时使用 records(C# 9+)。Records 自动生成 Equals、GetHashCode、ToString,支持 with 表达式。需要可变性、INotifyPropertyChanged 或 EF Core 实体时使用传统类。ASP.NET Core API DTO 推荐使用 records。

如何处理 C# 类中没有的未知 JSON 属性?

System.Text.Json 默认忽略未知属性。要捕获未知字段,添加 [JsonExtensionData] 标注的 Dictionary。Newtonsoft 默认抛出异常,需设置 MissingMemberHandling.Ignore。

将 JSON 转换为 C# 类是每个 .NET 开发者的基本技能。使用我们的免费在线 JSON 转 C# 类转换器即时生成代码,参考本指南了解最佳实践。

使用我们的免费在线工具即时将 JSON 转换为 C# 类。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

C#JSON to C#JVJSON to Java ClassTSJSON to TypeScript{ }JSON Formatter

相关文章

JSON转Java类转换器:POJO、Jackson、Gson和Lombok完整指南

在线将JSON转换为Java类。学习使用Jackson、Gson、Lombok和Java Records从JSON生成POJO的方法。

JSON 转 TypeScript:完整指南与示例

学习如何自动将 JSON 数据转换为 TypeScript 接口。涵盖嵌套对象、数组、可选字段和最佳实践。

JSON转Kotlin数据类:kotlinx.serialization、Moshi和Gson完整指南

在线将JSON转换为Kotlin数据类。学习使用kotlinx.serialization、Moshi和Gson进行JSON解析。