jtr/DevDisciples.Json.Tools/JsonPath.Interpreter.cs
mdnapo 82a59eebd2 - Refactored code
- Implemented basic JSON Path interpreter
- Clean up
2024-09-22 16:30:31 +02:00

167 lines
6.1 KiB
C#

using DevDisciples.Json.Parser;
using DevDisciples.Json.Parser.Syntax;
using DevDisciples.Parsing;
using DevDisciples.Parsing.Extensions;
namespace DevDisciples.Json.Tools;
public static partial class JsonPath
{
public static partial class Interpreter
{
private static readonly VisitorContainer<IJsonPathSyntax, Context> Visitors = new();
static Interpreter()
{
Visitors.Register<WildCardSyntax>(WildCardExpression);
Visitors.Register<PropertyAccessorSyntax>(PropertyAccessorExpression);
Visitors.Register<PropertySyntax>(PropertyExpression);
Visitors.Register<ArrayIndexSyntax>(ArrayIndexExpression);
Visitors.Register<ArrayIndexListSyntax>(ArrayIndexListExpression);
Visitors.Register<ObjectIndexSyntax>(ObjectIndexExpression);
Visitors.Register<ObjectIndexListSyntax>(ObjectIndexListExpression);
}
public static IJsonSyntax Evaluate(string source, string path)
{
var root = JsonParser.Parse("<json>", source);
if (root is not JsonArraySyntax && root is not JsonObjectSyntax)
throw Report.Error(root.Token, "Expected a JSON array or object.");
var expressions = Parser.Parse(path);
var context = new Context { Target = root };
foreach (var expr in expressions)
Visitors[expr.GetType()](expr, context);
return context.Target;
}
private static void Evaluate(IJsonPathSyntax node, Context context) => Visitors[node.GetType()](node, context);
private static void WildCardExpression(IJsonPathSyntax visitee, Context context)
{
switch (context.Target)
{
case JsonObjectSyntax @object:
var elements = @object.Properties.Select(p => p.Value).ToArray();
var target = new JsonArraySyntax(@object.Token, elements);
context.Target = target;
break;
case JsonArraySyntax array:
context.Target = array;
break;
default:
var syntax = visitee.As<WildCardSyntax>();
throw Report.Error(syntax.Token, "Invalid target for '*'.");
}
}
private static void PropertyAccessorExpression(IJsonPathSyntax visitee, Context context)
{
var accessor = visitee.As<PropertyAccessorSyntax>();
Evaluate(accessor.Getter, context);
}
private static void PropertyExpression(IJsonPathSyntax visitee, Context context)
{
var property = visitee.As<PropertySyntax>();
switch (context.Target)
{
case JsonObjectSyntax @object:
foreach (var objectProperty in @object.Properties)
{
if (objectProperty.Key.Lexeme != property.Token.Lexeme) continue;
context.Target = objectProperty.Value;
return;
}
context.Target = new JsonNullSyntax();
break;
default:
context.Target = new JsonNullSyntax();
break;
}
}
private static void ArrayIndexExpression(IJsonPathSyntax visitee, Context context)
{
if (context.Target is not JsonArraySyntax array)
throw Report.Error(context.Target.Token, "Integer indexes are only allowed on arrays.");
var index = visitee.As<ArrayIndexSyntax>();
var value = index.IndexAsInt;
if (value >= 0 && value < array.Elements.Length)
context.Target = array.Elements[value];
else throw Report.Error(index.Token, "Index out of range.");
}
private static void ArrayIndexListExpression(IJsonPathSyntax visitee, Context context)
{
var indices = visitee.As<ArrayIndexListSyntax>();
if (context.Target is not JsonArraySyntax array)
throw Report.Error(indices.Token, "Integer indices are only allowed on arrays.");
var list = new List<IJsonSyntax>();
for (var i = 0; i < indices.Indices.Length; i++)
{
var index = indices.ValueAt(i);
if (index >= 0 && index < array.Elements.Length)
list.Add(array.Elements.ElementAt(index));
}
context.Target = new JsonArraySyntax(array.Token, list.ToArray());
}
private static void ObjectIndexExpression(IJsonPathSyntax visitee, Context context)
{
if (context.Target is not JsonObjectSyntax @object)
throw Report.Error(context.Target.Token, "String indices are only allowed on objects.");
var index = visitee.As<ObjectIndexSyntax>();
foreach (var property in @object.Properties)
{
if (property.Key.Lexeme != index.Index.Lexeme) continue;
context.Target = property.Value;
return;
}
context.Target = new JsonNullSyntax(@object.Token);
}
private static void ObjectIndexListExpression(IJsonPathSyntax visitee, Context context)
{
if (context.Target is not JsonObjectSyntax @object)
throw Report.Error(context.Target.Token, "Index strings are only allowed on objects.");
var indices = visitee.As<ObjectIndexListSyntax>();
var elements = new List<IJsonSyntax>();
foreach (var index in indices.Indexes)
{
foreach (var property in @object.Properties)
{
if (property.Key.Lexeme != index.Lexeme) continue;
elements.Add(property.Value);
break;
}
}
context.Target = new JsonArraySyntax(@object.Token, elements.ToArray());
}
}
}