You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# System.Text.Json无法反序列化OpenSky API响应,DTO模型不匹配求助

问题根源分析

你遇到的核心问题是JSON结构与DTO模型不匹配:OpenSky API返回的states是纯值类型的二维数组(每个子数组是顺序排列的原始值),但你试图用带[JsonPropertyName("0")]这类索引属性的类来映射数组元素——System.Text.Json默认不支持将数组元素直接映射到类的属性上,这才导致反序列化失败,最终实例为null。

解决方案

下面提供两种可行的解决方式,推荐第一种(自定义转换器),更符合面向对象的代码风格:

方案一:自定义JsonConverter处理数组到对象的映射

1. 编写自定义转换器

这个转换器会读取JSON数组的每个元素,按顺序赋值给FlightStatusOpenSkyDto的属性:

public class FlightStatusOpenSkyDtoConverter : JsonConverter<FlightStatusOpenSkyDto>
{
    public override FlightStatusOpenSkyDto? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType != JsonTokenType.StartArray)
            return null;

        var dto = new FlightStatusOpenSkyDto();
        int index = 0;

        while (reader.Read())
        {
            if (reader.TokenType == JsonTokenType.EndArray)
                break;

            switch (index)
            {
                case 0:
                    dto.Icao24 = reader.TokenType == JsonTokenType.Null ? null : reader.GetString();
                    break;
                case 1:
                    dto.Callsign = reader.TokenType == JsonTokenType.Null ? null : reader.GetString()?.Trim();
                    break;
                case 2:
                    dto.OriginCountry = reader.TokenType == JsonTokenType.Null ? null : reader.GetString();
                    break;
                case 3:
                    dto.TimePosition = reader.TokenType == JsonTokenType.Null ? null : reader.GetInt32();
                    break;
                case 4:
                    dto.LastContact = reader.TokenType == JsonTokenType.Null ? null : reader.GetInt32();
                    break;
                case 5:
                    dto.Longitude = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 6:
                    dto.Latitude = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 7:
                    dto.BaroAltitude = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 8:
                    dto.OnGround = reader.TokenType == JsonTokenType.Null ? null : reader.GetBoolean();
                    break;
                case 9:
                    dto.Velocity = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 10:
                    dto.TrueTrack = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 11:
                    dto.VerticalRate = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 12:
                    dto.Sensors = reader.TokenType == JsonTokenType.Null ? null : JsonSerializer.Deserialize<int[]>(ref reader, options);
                    break;
                case 13:
                    dto.GeoAltitude = reader.TokenType == JsonTokenType.Null ? null : (float?)reader.GetDouble();
                    break;
                case 14:
                    dto.Squawk = reader.TokenType == JsonTokenType.Null ? null : reader.GetString();
                    break;
                case 15:
                    dto.Spi = reader.TokenType == JsonTokenType.Null ? null : reader.GetBoolean();
                    break;
                case 16:
                    dto.PositionSource = reader.TokenType == JsonTokenType.Null ? null : reader.GetInt32();
                    break;
                case 17:
                    dto.Category = reader.TokenType == JsonTokenType.Null ? null : reader.GetInt32();
                    break;
            }

            index++;
        }

        return dto;
    }

    public override void Write(Utf8JsonWriter writer, FlightStatusOpenSkyDto value, JsonSerializerOptions options)
    {
        // 若不需要序列化DTO,可留空;如需实现,按数组顺序写入对应值即可
        writer.WriteStartArray();
        writer.WriteStringValue(value.Icao24);
        writer.WriteStringValue(value.Callsign);
        writer.WriteStringValue(value.OriginCountry);
        writer.WriteNumberValue(value.TimePosition ?? 0);
        writer.WriteNumberValue(value.LastContact ?? 0);
        writer.WriteNumberValue(value.Longitude ?? 0);
        writer.WriteNumberValue(value.Latitude ?? 0);
        writer.WriteNumberValue(value.BaroAltitude ?? 0);
        writer.WriteBooleanValue(value.OnGround ?? false);
        writer.WriteNumberValue(value.Velocity ?? 0);
        writer.WriteNumberValue(value.TrueTrack ?? 0);
        writer.WriteNumberValue(value.VerticalRate ?? 0);
        if (value.Sensors != null) JsonSerializer.Serialize(writer, value.Sensors, options);
        else writer.WriteNullValue();
        writer.WriteNumberValue(value.GeoAltitude ?? 0);
        writer.WriteStringValue(value.Squawk);
        writer.WriteBooleanValue(value.Spi ?? false);
        writer.WriteNumberValue(value.PositionSource ?? 0);
        writer.WriteNumberValue(value.Category ?? 0);
        writer.WriteEndArray();
    }
}

2. 修改DTO模型

移除原来的[JsonPropertyName]索引属性,给FlightStatusOpenSkyDto添加转换器标记:

public class RootObjectOfFlightStatus
{
    [JsonPropertyName("time")]
    public long Time { get; set; }

    [JsonPropertyName("states")]
    public List<FlightStatusOpenSkyDto>? States { get; set; }
}

[JsonConverter(typeof(FlightStatusOpenSkyDtoConverter))]
public class FlightStatusOpenSkyDto
{
    public string? Icao24 { get; set; }
    public string? Callsign { get; set; }
    public string? OriginCountry { get; set; }
    public int? TimePosition { get; set; }
    public int? LastContact { get; set; }
    public float? Longitude { get; set; }
    public float? Latitude { get; set; }
    public float? BaroAltitude { get; set; }
    public bool? OnGround { get; set; }
    public float? Velocity { get; set; }
    public float? TrueTrack { get; set; }
    public float? VerticalRate { get; set; }
    public int[]? Sensors { get; set; }
    public float? GeoAltitude { get; set; }
    public string? Squawk { get; set; }
    public bool? Spi { get; set; }
    public int? PositionSource { get; set; }
    public int? Category { get; set; }
}

3. 反序列化时传入转换器选项

var options = new JsonSerializerOptions();
options.Converters.Add(new FlightStatusOpenSkyDtoConverter());
RootObjectOfFlightStatus? flightStatusOpenSkyDto = JsonSerializer.Deserialize<RootObjectOfFlightStatus>(response, options);

方案二:直接映射原始数组(快速实现)

如果不想编写自定义转换器,可以先将states反序列化为原始数组,再手动映射到DTO:

// 修改RootObject模型
public class RootObjectOfFlightStatus
{
    [JsonPropertyName("time")]
    public long Time { get; set; }

    [JsonPropertyName("states")]
    public List<object[]>? States { get; set; }
}

// 反序列化后手动转换
var rawData = JsonSerializer.Deserialize<RootObjectOfFlightStatus>(response);
var flightStatusList = rawData?.States?.Select(arr => new FlightStatusOpenSkyDto
{
    Icao24 = arr[0] as string,
    Callsign = (arr[1] as string)?.Trim(),
    OriginCountry = arr[2] as string,
    TimePosition = arr[3] as int?,
    LastContact = arr[4] as int?,
    Longitude = arr[5] as float?,
    Latitude = arr[6] as float?,
    BaroAltitude = arr[7] as float?,
    OnGround = arr[8] as bool?,
    Velocity = arr[9] as float?,
    TrueTrack = arr[10] as float?,
    VerticalRate = arr[11] as float?,
    Sensors = arr[12] as int[],
    GeoAltitude = arr[13] as float?,
    Squawk = arr[14] as string,
    Spi = arr[15] as bool?,
    PositionSource = arr[16] as int?,
    Category = arr[17] as int?
}).ToList();

内容的提问来源于stack exchange,提问作者Филипп Демиденко

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 15:58:19