如何用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
相关产品推荐
相关产品推荐

