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

如何用System.Text.Json实现支持点表示法的动态JSON对象适配Roslyn脚本?

解决System.Text.Json在Roslyn CSharpScript中支持点表示法访问JSON属性的方案

方案1:动态包装JsonElement(推荐)

给JsonElement做一个动态包装类,通过.NET的DynamicObject拦截点语法的访问请求,内部自动调用GetProperty获取属性,同时支持数组索引访问。

包装类代码

using System.Dynamic;
using System.Text.Json;

public class DynamicJsonElement : DynamicObject
{
    private readonly JsonElement _element;

    public DynamicJsonElement(JsonElement element)
    {
        _element = element;
    }

    // 拦截点语法访问属性
    public override bool TryGetMember(GetMemberBinder binder, out object? result)
    {
        if (_element.ValueKind != JsonValueKind.Object)
        {
            result = null;
            return false;
        }

        if (_element.TryGetProperty(binder.Name, out var property))
        {
            result = WrapElement(property);
            return true;
        }

        result = null;
        return false;
    }

    // 支持数组索引访问(比如 Payload.dogs[0].name)
    public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result)
    {
        if (_element.ValueKind != JsonValueKind.Array || indexes.Length != 1 || indexes[0] is not int index)
        {
            result = null;
            return false;
        }

        if (index >= 0 && index < _element.GetArrayLength())
        {
            var element = _element[index];
            result = WrapElement(element);
            return true;
        }

        result = null;
        return false;
    }

    // 根据JsonElement类型返回对应的值或包装对象
    private static object? WrapElement(JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => new DynamicJsonElement(element),
            JsonValueKind.Array => new DynamicJsonArray(element),
            JsonValueKind.String => element.GetString(),
            JsonValueKind.Number => element.TryGetInt64(out var longVal) ? longVal : element.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Null => null,
            _ => element.ToString()
        };
    }

    // 数组的动态包装类
    private class DynamicJsonArray : DynamicObject
    {
        private readonly JsonElement _arrayElement;

        public DynamicJsonArray(JsonElement arrayElement)
        {
            _arrayElement = arrayElement;
        }

        public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result)
        {
            if (indexes.Length != 1 || indexes[0] is not int index)
            {
                result = null;
                return false;
            }

            if (index >= 0 && index < _arrayElement.GetArrayLength())
            {
                var element = _arrayElement[index];
                result = WrapElement(element);
                return true;
            }

            result = null;
            return false;
        }
    }
}

使用方式

解析JSON后把JsonElement包装成动态对象,传入Roslyn脚本的上下文:

// 示例JSON
var json = "{\"Payload\": {\"dog\": {\"name\": \"旺财\"}, \"dogs\": [{\"name\": \"来福\"}]}}";
using var doc = JsonDocument.Parse(json);
var rootElement = doc.RootElement;

// 包装成动态对象
dynamic dynamicRoot = new DynamicJsonElement(rootElement);

// 定义脚本上下文类
public class Globals
{
    public dynamic? Payload { get; set; }
}

// 执行Roslyn脚本,支持点语法和数组索引
var script = CSharpScript.Create("Payload.dog.name + \" 和 \" + Payload.dogs[0].name", globalsType: typeof(Globals));
var result = await script.RunAsync(new Globals { Payload = dynamicRoot.Payload });
Console.WriteLine(result.ReturnValue); // 输出:旺财 和 来福

方案2:转成ExpandoObject

把整个JSON结构递归转换成ExpandoObject(动态类型),这样也能直接用点语法访问属性。适合JSON结构不复杂、性能要求不高的场景。

转换扩展方法

using System.Dynamic;
using System.Text.Json;

public static class JsonElementExtensions
{
    public static object? ToDynamic(this JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => element.ToExpandoObject(),
            JsonValueKind.Array => element.EnumerateArray().Select(ToDynamic).ToList(),
            JsonValueKind.String => element.GetString(),
            JsonValueKind.Number => element.TryGetInt64(out var longVal) ? longVal : element.GetDouble(),
            JsonValueKind.True => true,
            JsonValueKind.False => false,
            JsonValueKind.Null => null,
            _ => element.ToString()
        };
    }

    private static ExpandoObject ToExpandoObject(this JsonElement element)
    {
        var expando = new ExpandoObject();
        var expandoDict = (IDictionary<string, object?>)expando;

        foreach (var property in element.EnumerateObject())
        {
            expandoDict[property.Name] = property.Value.ToDynamic();
        }

        return expando;
    }
}

使用方式

var json = "{\"Payload\": {\"dog\": {\"name\": \"旺财\"}}}";
using var doc = JsonDocument.Parse(json);
dynamic dynamicRoot = doc.RootElement.ToDynamic();

var script = CSharpScript.Create("Payload.dog.name", globalsType: typeof(Globals));
var result = await script.RunAsync(new Globals { Payload = dynamicRoot.Payload });
Console.WriteLine(result.ReturnValue); // 输出:旺财

方案3:自定义扩展方法(妥协方案)

如果不想用动态类型,可以给JsonElement写个简洁的扩展方法,替代GetProperty。比如:

public static class JsonElementExtensions
{
    public static JsonElement Get(this JsonElement element, string propertyName)
    {
        return element.GetProperty(propertyName);
    }
}

然后脚本里可以写成Payload.Get("dog").Get("name"),虽然不是点语法,但比原生GetProperty简洁。

内容的提问来源于stack exchange,提问作者Jorgoth

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 14:53:13