当前位置: 首页 > news >正文

实现 json path 来评估函数式解析器的损耗

简要说明函数式解析器

这里以最简单 json path "$.Name" 举例,

我们所需要解析即为 $ 开头, . 之后字符为属性名

换成代码大致为 Parser = Char('$').And(Char('.')).And(AnyExclude("[]().,\" '\r\n@$!=<\\?&|*:")).Eof()

像 Char And 这些方法在面向对象中我们会让方法生成解析算子实例,以达到与函数式相同效果, 这也是性能损耗的一个大点

比如 Char 方法

 public static Parser<char> Char(char c) => new CharLiteral(c);

具体算子实现

public class CharLiteral : Parser<char>
{private SearchValues<char> c;public string Value { get; private set; }public CharLiteral(char c){this.c = SearchValues.Create(new char[] { c });Value = c.ToString();}public CharLiteral(string c){this.c = SearchValues.Create(c);Value = c;}public override bool Parse(CharParseContext context, ref ParseResult<char> result){context.EnterParser(this);var cursor = context.Cursor;if (!cursor.Eof && c.Contains(cursor.Current)){var c = cursor.Current;var start = cursor.Offset;cursor.Advance();result.Set(start, cursor.Offset, c);context.ExitParser(this);return true;}context.ExitParser(this);return false;}
}

好了,借用经典话语 这是功,这是防,这是boss 去吧

按照 [rfc9535](https://www.rfc-editor.org/rfc/rfc9535.html) 标准, 最终 json path 解析器大致如下:


public class JsonPathParser
{//public static Parser<char> B = Char(new char[]//{ (char)0x20, // Space//  (char)0x09, //Horizontal tab//  (char)0x0A, // Line feed or New line//  (char)0x0D // Carriage return//});public static readonly Parser<char> RootIdentifier = Char('$').Name(nameof(RootIdentifier));public static readonly Parser<int> Int = Int().Name(nameof(Int));public static readonly Parser<char> DoubleQuoted = Char('"').Name(nameof(DoubleQuoted));public static readonly Parser<char> SingleQuoted = Char('\'').Name(nameof(SingleQuoted));public static readonly Parser<IStatement> WildcardSelector = Char('*').Then<IStatement>(static x => new WildcardSelectorStatment()).Name(nameof(WildcardSelector));public static readonly Parser<IStatement> IndexSelector = Int.Then<IStatement>(static x => new IndexSelectorStatment() { Index = x }).Name(nameof(IndexSelector));public static readonly Parser<TextSpan> StringLiteral = Between(DoubleQuoted, ZeroOrOne(Any("\"", mustHasEnd: true, escape: '\\')), DoubleQuoted).Or(Between(SingleQuoted, ZeroOrOne(Any("'", mustHasEnd: true, escape: '\\')), SingleQuoted)).Name(nameof(StringLiteral));public static readonly Parser<IStatement> NameSelector = StringLiteral.Then<IStatement>(static x => new Member() { Name = x.Span.ToString() }).Name(nameof(NameSelector));public static readonly Parser<int> Start = Int;public static readonly Parser<int> End = Int;public static readonly Parser<int> Step = Int;public static readonly Parser<Nothing> S = IgnoreChar(new char[]{ (char)0x20, // Space(char)0x09, //Horizontal tab(char)0x0A, // Line feed or New line(char)0x0D // Carriage return}).Name(nameof(S));public static readonly Parser<char> CurrentNodeIdentifier = Char('@').Name(nameof(CurrentNodeIdentifier));public static readonly Parser<char> LogicalNotOp = Char('!').Name(nameof(LogicalNotOp));public static readonly Parser<string> ComparisonOp = Text("==").Or(Text("!=")).Or(Text("<=")).Or(Text(">=")).Or(Text("<")).Or(Text(">")).Name(nameof(ComparisonOp));public static readonly Parser<IStatement> Num = Decimal(NumberOptions.Float).Then<IStatement>(static x => new NumberValue(x)).Name(nameof(Num));public static readonly Parser<IStatement> True = Text("true").Then<IStatement>(static x => BoolValue.True).Name(nameof(True));public static readonly Parser<IStatement> False = Text("false").Then<IStatement>(static x => BoolValue.False).Name(nameof(False));public static readonly Parser<IStatement> Null = Text("null").Then<IStatement>(static x => NullValue.Value).Name(nameof(Null));private const string name = "[]().,\" '\r\n@$!=<\\?&|*:";//public static Parser<char> LCALPHA = Char('a', 'z');//public static Parser<char> DIGIT = Char('0', '9');//public static Parser<char> ALPHA = Char((char)0x41, (char)0x5A).Or(Char((char)0x61, (char)0x7A));public static readonly Parser<IStatement> MemberNameShorthand = AnyExclude(name).Then<IStatement>(static x => new Member { Name = x.Span.ToString() }).Name(nameof(MemberNameShorthand));//public static Parser<char> NameFirst = ALPHA.Or(Char('_')).Or(Char((char)0x80, (char)0xD7FF)).Or(Char((char)0xE000, (char)0xFFFF));//public static Parser<char> NameChar = NameFirst.Or(DIGIT);//public static Parser<char> FunctionNameFirst = LCALPHA;//public static Parser<char> FunctionNameChar = FunctionNameFirst.Or(Char('_')).Or(DIGIT);//public static Parser<string> FunctionName = FunctionNameFirst.And(ZeroOrMany(FunctionNameChar)).Then<string>(static x => throw new NotImplementedException());public static readonly Parser<string> FunctionName = AnyExclude(name).Then<string>(static x => x.Span.ToString()).Name(nameof(FunctionName));public static readonly Parser<IStatement> SliceSelector = Optional<int?>(Start.And(S).Then<int?>(static x => x.Item1), null).And(Char(':')).And(S).And(Optional<int?>(End.And(S).Then<int?>(static x => x.Item1), null)).And(Optional<int?>(Char(':').And(Optional<int?>(S.And(Step).Then<int?>(static x => x.Item2), null)).Then<int?>(static x => x.Item2))).Then<IStatement>(static x => new SliceStatement() { Start = x.Item1, End = x.Item4, Step = x.Item5 }).Name(nameof(SliceSelector));public static readonly Deferred<IStatement> LogicalExpr = Deferred<IStatement>(nameof(LogicalExpr));public static readonly Parser<IStatement> FilterSelector = Char('?').And(S).And(LogicalExpr).Then<IStatement>(static x => new FilterSelectorStatement(){Statement = x.Item3}).Name(nameof(FilterSelector));public static readonly Parser<IStatement> Selector = NameSelector.Or(WildcardSelector).Or(SliceSelector).Or(IndexSelector).Or(FilterSelector).Name(nameof(Selector));public static readonly Parser<IStatement> ParenExpr = Optional(LogicalNotOp.And(S)).And(Char('(')).And(S).And(LogicalExpr).And(S).And(Char(')')).Then<IStatement>(static x => new UnaryOperaterStatement(){Operator = x.Item1.Item1 == '!' ? "!" : "(",Statement = x.Item4}).Name(nameof(ParenExpr));public static readonly Deferred<IReadOnlyList<(Nothing, IStatement)>> Segments = Deferred<IReadOnlyList<(Nothing, IStatement)>>(nameof(Segments));public static readonly Deferred<IStatement> FunctionExpr = Deferred<IStatement>(nameof(FunctionExpr));public static readonly Deferred<IStatement> JsonPathQuery = Deferred<IStatement>(nameof(JsonPathQuery));public static readonly Parser<IStatement> RelQuery = CurrentNodeIdentifier.And(Segments).Then<IStatement>(static x => new CurrentNode() { Child = ConvertSegments(x.Item2) }).Name(nameof(RelQuery));public static readonly Parser<IStatement> Literal = Num.Or(StringLiteral.Then<IStatement>(static x => new StringValue(x.Span.ToString()))).Or(True).Or(False).Or(Null).Name(nameof(Literal));public static readonly Parser<IStatement> NameSegment = Char('[').And(NameSelector).And(Char(']')).Then<IStatement>(static x => x.Item2).Or(Char('.').And(MemberNameShorthand).Then<IStatement>(static x => x.Item2)).Name(nameof(NameSegment));public static readonly Parser<IStatement> IndexSegment = Char('[').And(IndexSelector).And(Char(']')).Then<IStatement>(static x => x.Item2).Name(nameof(IndexSegment));public static readonly Parser<IStatement> SingularQuerySegments = ZeroOrMany(S.And(NameSegment.Or(IndexSegment))).Then<IStatement>(ConvertSegments).Name(nameof(SingularQuerySegments));public static readonly Parser<IStatement> RelSingularQuery = CurrentNodeIdentifier.And(SingularQuerySegments).Then<IStatement>(static x => new CurrentNode() { Child = x.Item2 }).Name(nameof(RelSingularQuery));public static readonly Parser<IStatement> AbsSingularQuery = RootIdentifier.And(SingularQuerySegments).Then<IStatement>(static x => new RootNode() { Child = x.Item2 }).Name(nameof(AbsSingularQuery));public static readonly Parser<IStatement> SingularQuery = RelSingularQuery.Or(AbsSingularQuery).Name(nameof(SingularQuery));public static readonly Parser<IStatement> Comparable = Literal.Or(SingularQuery).Or(FunctionExpr).Name(nameof(Comparable));public static readonly Parser<IStatement> ComparisonExpr = Comparable.And(S).And(ComparisonOp).And(S).And(Comparable).Then<IStatement>(static x => new OperatorStatement() { Left = x.Item1, Operator = x.Item3, Right = x.Item5 }).Name(nameof(ComparisonExpr));public static readonly Parser<IStatement> FilterQuery = RelQuery.Or(JsonPathQuery).Name(nameof(FilterQuery));public static readonly Parser<IStatement> FunctionArgument = FilterQuery.Or(LogicalExpr).Or(FunctionExpr).Or(Literal).Name(nameof(FunctionArgument));public static readonly Parser<IStatement> TestExpr = Optional(LogicalNotOp.And(S)).And(FilterQuery.Or(FunctionExpr)).Then<IStatement>(static x => x.Item1.Item1 == '!' ? new UnaryOperaterStatement() { Operator = "!", Statement = x.Item2 } : x.Item2).Name(nameof(TestExpr));public static readonly Parser<IStatement> BasicExpr = ParenExpr.Or(ComparisonExpr).Or(TestExpr).Name(nameof(BasicExpr));public static readonly Parser<IStatement> LogicalAndExpr = BasicExpr.And(ZeroOrMany(S.And(Text("&&")).And(S).And(BasicExpr))).Then<IStatement>(static x =>{IStatement current = x.Item1;if (x.Item2 != null && x.Item2.Count > 0){foreach (var item in x.Item2){current = new AndStatement() { Left = current, Right = item.Item4 };}}return current;}).Name(nameof(LogicalAndExpr));public static readonly Parser<IStatement> LogicalOrExpr = LogicalAndExpr.And(ZeroOrMany(S.And(Text("||")).And(S).And(LogicalAndExpr))).Then<IStatement>(static x =>{IStatement current = x.Item1;if (x.Item2 != null && x.Item2.Count > 0){foreach (var item in x.Item2){current = new OrStatement() { Left = current, Right = item.Item4 };}}return current;}).Name(nameof(LogicalOrExpr));public static readonly Parser<IStatement> BracketedSelection = Char('[').And(S).And(Selector).And(ZeroOrMany(S.And(Char(',')).And(S).And(Selector))).And(S).And(Char(']')).Then<IStatement>(static x =>{var list = new List<IStatement> { x.Item3 };if (x.Item4 != null)list.AddRange(x.Item4.Select(y => y.Item4));if (list.Count == 0)return null;return list.Count == 1 ? list[0] : new UnionSelectionStatement(list);}).Name(nameof(BracketedSelection));public static readonly Parser<IStatement> ChildSegment = BracketedSelection.Or(Char('.').And(WildcardSelector.Or(MemberNameShorthand)).Then<IStatement>(static x => x.Item2)).Name(nameof(ChildSegment));public static readonly Parser<IStatement> DescendantSegment = Char('.').And(Char('.')).And(BracketedSelection.Or(WildcardSelector).Or(MemberNameShorthand)).Then<IStatement>(static x => new WildcardSelectorStatment() { Child = x.Item3 }).Name(nameof(DescendantSegment));public static readonly Parser<IStatement> Segment = ChildSegment.Or(DescendantSegment).Name(nameof(Segment));public static readonly Parser<IStatement> Parser;static JsonPathParser(){LogicalExpr.Parser = LogicalOrExpr;Segments.Parser = ZeroOrMany(S.And(Segment));//MemberNameShorthand.Parser = NameFirst.And(ZeroOrMany(NameChar)).Then<IStatement>(static x => new Member { Name = x.Item1 + new string(x.Item2.ToArray()) });FunctionExpr.Parser = FunctionName.And(Char('(')).And(S).And(Optional(FunctionArgument.And(ZeroOrMany(S.And(Char(',')).And(S).And(FunctionArgument))))).And(S).And(Char(')')).Then<IStatement>(static x =>{var args = new List<IStatement>();if (x.Item4.Item1 != null){args.Add(x.Item4.Item1);}if (x.Item4.Item2 != null){args.AddRange(x.Item4.Item2.Select(y => y.Item4));}var func = new FunctionStatement(){Name = x.Item1,Arguments = args.Count == 0 ? Array.Empty<IStatement>() : args.ToArray()};return func;});JsonPathQuery.Parser = RootIdentifier.And(Segments).Then<IStatement>(static x => new RootNode() { Child = ConvertSegments(x.Item2) });Parser = JsonPathQuery.Eof().Name(nameof(Parser));}private static IStatement ConvertSegments(IReadOnlyList<(Nothing, IStatement)> x){if (x == null || x.Count == 0){return null;}else if (x.Count == 1)return x[0].Item2;else{var current = x.Last().Item2;for (int i = x.Count - 2; i >= 0; i--){if (x[i].Item2 is IParentStatement p){var pp = p;while (pp.Child != null){var pc = p.Child as IParentStatement;if (pc is null)throw new NotSupportedException($"Cannot set child for statement of type {p.GetType().FullName}");pp = pc;}pp.Child = current;current = p;}else{throw new NotSupportedException($"Cannot set child for statement of type {x[i].Item2.GetType().FullName}");}}return current;}}
}

性能测试

测试代码

[MemoryDiagnoser, GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
public class JsonPathBenchmarks
{private object data = new{Num = -3.4,Nu = null as string,Array = new object[]{new { Name = "Alice", Age = 30 },new { Name = "Bob", Age = 25 },new { Name = "Charlie", Age = 35 }},};private string path = "$.Array[1]['Name','Age']";private string json;private IStatement cache;private readonly JsonPath pc;public JsonPathBenchmarks(){json = JsonSerializer.Serialize(data);JsonPathParser.Parser.TryParseResult(path, out var result, out var error);cache = result.Value;pc = JsonPath.Parse(path);}[Benchmark]public object CacheTest(){return cache.EvaluateJson(json);}[Benchmark]public object NoCacheTest(){JsonPathParser.Parser.TryParseResult(path, out var result, out var error);return result.Value.EvaluateJson(json);}private Newtonsoft.Json.Linq.JToken testTo = Newtonsoft.Json.Linq.JToken.Parse("null");[Benchmark]public object NewtonsoftOnlyParseTest(){return testTo.SelectTokens(path);}[Benchmark]public object NewtonsoftTest(){Newtonsoft.Json.Linq.JToken token = Newtonsoft.Json.Linq.JToken.Parse(json);return token.SelectTokens(path);}[Benchmark]public object JsonPathNetTest(){var p = JsonPath.Parse(path);var instance = JsonNode.Parse(json);return p.Evaluate(instance);}[Benchmark]public object JsonPathNetCacheTest(){var instance = JsonNode.Parse(json);return pc.Evaluate(instance);}[Benchmark]public object JsonPathNetOnlyParseTest(){return JsonPath.Parse(path);}[Benchmark]public object OnlyParseTest(){JsonPathParser.Parser.TryParseResult(path, out var result, out var error);return result.Value;}
}

效果


BenchmarkDotNet v0.15.4, Windows 11 (10.0.26100.6584/24H2/2024Update/HudsonValley)
Intel Core i7-10700 CPU 2.90GHz, 1 CPU, 16 logical and 8 physical cores
.NET SDK 9.0.304[Host]     : .NET 9.0.8 (9.0.8, 9.0.825.36511), X64 RyuJIT x86-64-v3DefaultJob : .NET 9.0.8 (9.0.8, 9.0.825.36511), X64 RyuJIT x86-64-v3

嗯,在 system.text.json 上解析 比 Newtonsoft.json 还是快了点,具体实现都是最基本的,没有像 jsonpath 那些做优化设计,性能感觉还是可以的

文章转载自:victor.x.qu

原文链接:https://www.cnblogs.com/fs7744/p/19194268

体验地址:http://www.jnpfsoft.com/?from=001YH

http://www.dtcms.com/a/578597.html

相关文章:

  • 微网站分销linux做网站哪个版本好
  • 解决Git 冲突后本地提交丢失/未推送问题
  • 企业做网站建设遇到的问题合肥长丰路网站建设
  • 【剑斩OFFER】算法的暴力美学——最小覆盖字串
  • 全屏网站模板制作教程国外网站需要备案吗
  • 免费做网站有哪些家SaaS网站可以做seo嘛
  • 14:C++:二叉搜索树
  • 「日拱一码」142 Lasso调参注意事项与技巧
  • 【OTA专题】1 OTA加密升级总览
  • 针对编程面试和算法题的基础书籍
  • 10.大模型Agent介绍与应用
  • 兵团建设环保局门户网站中交通力建设股份有限公司网站
  • seo网站程序手机网站生成app客户端
  • 做旅游网站的论文wordpress公告模板
  • 博客登陆wordpress廊坊企业网站排名优化
  • 【LLaVA-NeXT】请问,这种“auto分配”的行为具体是哪一个库的API实现的呢
  • Riverpod框架内部实现原理剖析
  • 图解Redis面试篇
  • 网站首页设计风格wap网站源码下载
  • 获取泛型信息及获取注解信息
  • 会展免费网站模板网站优化课程
  • 【赵渝强老师】Redis数据的迁移
  • Rust编程学习 - 为什么说Cow 代表的是Copy-On-Write, 即“写时复制技术”,它是一种高效的 资源管理手段
  • Rust开发完全指南:从入门到与Python高效融合
  • 石家庄免费建站模板我不想找之前做网站的续费
  • 商城网站模板 免费五个跨境电商平台
  • 无人设备遥控器之数字图传技术
  • 哪个网站用织梦做的2017网站开发就业前景
  • 网站设计公司深圳ppt的网站导航栏怎么做的
  • React中的componentWillUnmount 使用