用C# 11的列表模式实现一个解析JSON路径的递归函数

解读

国内Unity面试里,“JSON路径解析”是热更脚本、配置系统、网络协议落地的刚需。
题目表面考
C# 11列表模式
,实则同时验证三点:

  1. 递归与不可变数据的驾驭能力——避免Unity主线程GC;
  2. System.Text.Json新API的熟悉度——Unity 2022 LTS已官方支持,替代Newtonsoft可减包体;
  3. Unity实际约束的敏感度——必须返回可空引用类型,且不能触发JIT限制(WebGL、iOS IL2CPP)。
    因此,答案要兼顾语法糖、性能、Unity平台合规

知识点

  • C# 11列表模式(List Pattern)[var head, .. var tail] 在switch表达式里解构JsonElement数组,零堆分配
  • System.Text.Json.JsonElement:Unity 2022.3+内置,不依赖Newtonsoft,可直接读Span避免字符串拼接。
  • 递归基例与尾递归:在IL2CPP下,编译器仍会把尾递归优化成循环,防止iOS栈溢出
  • 可空引用类型(#nullable enable):Unity 2021+正式支持,标记路径不存在时返回null,符合国内代码审计规范。
  • 热更兼容:代码里不出现dynamic、反射Emit,确保ToLua、HybridCLR、ILRuntime都能直接翻译。

答案

#nullable enable
using System;
using System.Text.Json;

public static class JsonPath
{
    /// <summary>
    ///  用C# 11列表模式递归解析JSON路径
    ///  示例:JsonPath.Resolve(root, "data", "0", "name")
    /// </summary>
    public static JsonElement? Resolve(JsonElement root, params ReadOnlySpan<string> path)
    {
        // 空路径直接返回根
        if (path.IsEmpty) return root;

        // 列表模式解构:head是首段,tail是剩余
        switch (path)
        {
            // 递归基例:只剩一段
            case [var head]:
                return head switch
                {
                    // 属性访问
                    _ when root.TryGetProperty(head, out var p) => p,
                    // 数组索引
                    _ when int.TryParse(head, out var idx) &&
                          root.ValueKind == JsonValueKind.Array &&
                          idx >= 0 && idx < root.GetArrayLength()
                        => root[idx],
                    _ => null
                };

            // 递归步骤:先解head,再对tail递归
            case [var head, .. var tail]:
                var next = head switch
                {
                    _ when root.TryGetProperty(head, out var p) => p,
                    _ when int.TryParse(head, out var idx) &&
                          root.ValueKind == JsonValueKind.Array &&
                          idx >= 0 && idx < root.GetArrayLength()
                        => root[idx],
                    _ => default(JsonElement?)
                };
                return next.HasValue ? Resolve(next.Value, tail) : null;

            // 兜底
            default:
                return null;
        }
    }
}

使用示例

var doc = JsonDocument.Parse(jsonText);
var name = JsonPath.Resolve(doc.RootElement, "config", "players", "0", "nickname");
Debug.Log(name?.GetString() ?? "路径不存在");

亮点

  • 列表模式让路径解构一目了然,无substring/Split,零GC;
  • **ReadOnlySpan<string>**传参,避免Unity主线程分配
  • 可空引用类型TryGetProperty组合,符合国内代码规范

拓展思考

  1. 性能极限:若路径深度>8,可改用迭代+Stack<ValueTuple>,把递归改循环,IL2CPP栈帧更稳
  2. 出错信息:国内策划配表常写错路径,可返回自定义record struct JsonPathResult(JsonElement? Value, string? Error)日志直接打印Error,减少QA往返。
  3. Burst编译:若把JsonElement换成Unity.Mathematics.bool4等blittable结构,可让JobSystem+Burst在后台线程批量解析,主线程零开销
  4. 热更兼容:ToLua不支持C# 11语法,预编译阶段用Source Generator把列表模式展开成if-else热更DLL无感知