- Refactored code
- Implemented basic JSON Path interpreter - Clean up
This commit is contained in:
parent
78ebafb409
commit
82a59eebd2
@ -1,4 +1,6 @@
|
||||
namespace DevDisciples.Json.Parser.Tests;
|
||||
using DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Tests;
|
||||
|
||||
public class JsonParserTests
|
||||
{
|
||||
@ -12,7 +14,7 @@ public class JsonParserTests
|
||||
{
|
||||
const string source = "null";
|
||||
var node = JsonParser.Parse(nameof(Can_parse_null), source);
|
||||
Assert.That(node is JsonNull);
|
||||
Assert.That(node is JsonNullSyntax);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -25,8 +27,8 @@ public class JsonParserTests
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(trueNode is JsonBool { Value: true });
|
||||
Assert.That(falseNode is JsonBool { Value: false });
|
||||
Assert.That(trueNode is JsonBoolSyntax { Value: true });
|
||||
Assert.That(falseNode is JsonBoolSyntax { Value: false });
|
||||
});
|
||||
}
|
||||
|
||||
@ -35,15 +37,15 @@ public class JsonParserTests
|
||||
{
|
||||
const string source = "1";
|
||||
var node = JsonParser.Parse(nameof(Can_parse_an_integer), source);
|
||||
Assert.That(node is JsonNumber { Value: 1 });
|
||||
Assert.That(node is JsonNumberSyntax { Value: 1 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_lex_a_decimal()
|
||||
public void Can_parse_a_decimal()
|
||||
{
|
||||
const string source = "1.0";
|
||||
var node = JsonParser.Parse(nameof(Can_lex_a_decimal), source);
|
||||
Assert.That(node is JsonNumber { Value: 1.0 });
|
||||
var node = JsonParser.Parse(nameof(Can_parse_a_decimal), source);
|
||||
Assert.That(node is JsonNumberSyntax { Value: 1.0 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -51,7 +53,7 @@ public class JsonParserTests
|
||||
{
|
||||
const string source = "\"Hello world!\"";
|
||||
var tokens = JsonParser.Parse(nameof(Can_parse_a_string), source);
|
||||
Assert.That(tokens is JsonString { Value: "Hello world!" });
|
||||
Assert.That(tokens is JsonStringSyntax { Value: "Hello world!" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -59,7 +61,7 @@ public class JsonParserTests
|
||||
{
|
||||
const string source = "[]";
|
||||
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_array), source);
|
||||
Assert.That(tokens is JsonArray);
|
||||
Assert.That(tokens is JsonArraySyntax);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -68,9 +70,9 @@ public class JsonParserTests
|
||||
const string source = "[1]";
|
||||
var tokens = JsonParser.Parse(nameof(Can_parse_a_single_item_array), source);
|
||||
|
||||
Assert.That(tokens is JsonArray
|
||||
Assert.That(tokens is JsonArraySyntax
|
||||
{
|
||||
Elements: [JsonNumber { Value: 1 }]
|
||||
Elements: [JsonNumberSyntax { Value: 1 }]
|
||||
});
|
||||
}
|
||||
|
||||
@ -80,15 +82,15 @@ public class JsonParserTests
|
||||
const string source = "[1,true,{},[],null]";
|
||||
var node = JsonParser.Parse(nameof(Can_parse_an_array_with_multiple_items), source);
|
||||
|
||||
Assert.That(node is JsonArray
|
||||
Assert.That(node is JsonArraySyntax
|
||||
{
|
||||
Elements:
|
||||
[
|
||||
JsonNumber { Value: 1 },
|
||||
JsonBool { Value: true },
|
||||
JsonObject { Properties.Length: 0 },
|
||||
JsonArray { Elements.Length: 0 },
|
||||
JsonNull
|
||||
JsonNumberSyntax { Value: 1 },
|
||||
JsonBoolSyntax { Value: true },
|
||||
JsonObjectSyntax { Properties.Length: 0 },
|
||||
JsonArraySyntax { Elements.Length: 0 },
|
||||
JsonNullSyntax
|
||||
]
|
||||
});
|
||||
}
|
||||
@ -98,7 +100,7 @@ public class JsonParserTests
|
||||
{
|
||||
const string source = "{}";
|
||||
var tokens = JsonParser.Parse(nameof(Can_parse_an_empty_object), source);
|
||||
Assert.That(tokens is JsonObject { Properties.Length: 0 });
|
||||
Assert.That(tokens is JsonObjectSyntax { Properties.Length: 0 });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -107,10 +109,10 @@ public class JsonParserTests
|
||||
const string source = "{\"first_name\":\"John\"}";
|
||||
var node = JsonParser.Parse(nameof(Can_parse_an_object_with_one_entry), source);
|
||||
|
||||
Assert.That(node is JsonObject { Properties.Length: 1 });
|
||||
var @object = (JsonObject)node;
|
||||
Assert.That(@object.Properties.Any(property => property.Key == "first_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" });
|
||||
Assert.That(node is JsonObjectSyntax { Properties.Length: 1 });
|
||||
var @object = (JsonObjectSyntax)node;
|
||||
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" });
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -121,12 +123,12 @@ public class JsonParserTests
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(node is JsonObject { Properties.Length: 2 });
|
||||
var @object = (JsonObject)node;
|
||||
Assert.That(@object.Properties.Any(property => property.Key == "first_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key == "first_name").Value is JsonString { Value: "John" });
|
||||
Assert.That(@object.Properties.Any(property => property.Key == "last_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key == "last_name").Value is JsonString { Value: "Doe" });
|
||||
Assert.That(node is JsonObjectSyntax { Properties.Length: 2 });
|
||||
var @object = (JsonObjectSyntax)node;
|
||||
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" });
|
||||
Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "last_name"));
|
||||
Assert.That(@object.Properties.First(property => property.Key.Lexeme == "last_name").Value is JsonStringSyntax { Value: "Doe" });
|
||||
});
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly struct JsonArray : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public ISyntaxNode[] Elements { get; }
|
||||
|
||||
public JsonArray(Lexer<JsonToken>.Token token, ISyntaxNode[] elements)
|
||||
{
|
||||
Token = token;
|
||||
Elements = elements;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly struct JsonBool : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public bool Value => bool.TryParse(Token.Lexeme, out var val) && val;
|
||||
|
||||
public JsonBool(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly struct JsonNull : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
|
||||
public JsonNull(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly struct JsonNumber : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default;
|
||||
|
||||
public JsonNumber(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly partial struct JsonObject
|
||||
{
|
||||
public readonly struct Property : ISyntaxNode
|
||||
{
|
||||
public string Key { get; }
|
||||
public ISyntaxNode Value { get; }
|
||||
|
||||
public Property(string key, ISyntaxNode value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly partial struct JsonObject : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public Property[] Properties { get; }
|
||||
|
||||
public JsonObject(Lexer<JsonToken>.Token token, Property[] properties)
|
||||
{
|
||||
Token = token;
|
||||
Properties = properties;
|
||||
}
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
using DevDisciples.Parsing;
|
||||
using DevDisciples.Json.Parser.Syntax;
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public static partial class JsonParser
|
||||
{
|
||||
public static ISyntaxNode Parse(string file, string source)
|
||||
public static IJsonSyntax Parse(string file, string source)
|
||||
{
|
||||
var tokens = JsonLexer.Default.Lex(file, source).ToArray();
|
||||
var context = new Context(tokens);
|
||||
@ -12,33 +13,33 @@ public static partial class JsonParser
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private static ISyntaxNode Expression(ParserContext<JsonToken> ctx)
|
||||
private static IJsonSyntax Expression(ParserContext<JsonToken> ctx)
|
||||
{
|
||||
if (ctx.Match(JsonToken.LeftBracket))
|
||||
return Array(ctx);
|
||||
return ArrayExpression(ctx);
|
||||
|
||||
if (ctx.Match(JsonToken.LeftBrace))
|
||||
return Object(ctx);
|
||||
return ObjectExpression(ctx);
|
||||
|
||||
if (ctx.Match(JsonToken.Minus) || ctx.Match(JsonToken.Number))
|
||||
return Number(ctx);
|
||||
return NumberExpression(ctx);
|
||||
|
||||
if (ctx.Match(JsonToken.String))
|
||||
return String(ctx);
|
||||
return StringExpression(ctx);
|
||||
|
||||
if (ctx.Match(JsonToken.Null))
|
||||
return Null(ctx);
|
||||
return NullExpression(ctx);
|
||||
|
||||
if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False))
|
||||
return Bool(ctx);
|
||||
return BoolExpression(ctx);
|
||||
|
||||
throw Report.Error(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'");
|
||||
}
|
||||
|
||||
private static ISyntaxNode Array(ParserContext<JsonToken> ctx)
|
||||
private static IJsonSyntax ArrayExpression(ParserContext<JsonToken> ctx)
|
||||
{
|
||||
var previous = ctx.Previous();
|
||||
List<ISyntaxNode>? elements = null;
|
||||
List<IJsonSyntax>? elements = null;
|
||||
|
||||
if (!ctx.Check(JsonToken.RightBracket))
|
||||
{
|
||||
@ -51,13 +52,13 @@ public static partial class JsonParser
|
||||
|
||||
ctx.Consume(JsonToken.RightBracket, "Expected ']'");
|
||||
|
||||
return new JsonArray(previous, elements?.ToArray() ?? System.Array.Empty<ISyntaxNode>());
|
||||
return new JsonArraySyntax(previous, elements?.ToArray() ?? System.Array.Empty<IJsonSyntax>());
|
||||
}
|
||||
|
||||
private static ISyntaxNode Object(ParserContext<JsonToken> ctx)
|
||||
private static IJsonSyntax ObjectExpression(ParserContext<JsonToken> ctx)
|
||||
{
|
||||
var previous = ctx.Previous();
|
||||
Dictionary<string, ISyntaxNode>? properties = null;
|
||||
Dictionary<Lexer<JsonToken>.Token, IJsonSyntax>? properties = null;
|
||||
|
||||
if (!ctx.Check(JsonToken.RightBrace))
|
||||
{
|
||||
@ -66,32 +67,32 @@ public static partial class JsonParser
|
||||
var key = ctx.Consume(JsonToken.String, "Expected property name");
|
||||
ctx.Consume(JsonToken.Colon, "Expected ':' after property name");
|
||||
properties ??= new();
|
||||
properties[key.Lexeme] = Expression(ctx);
|
||||
properties[key] = Expression(ctx);
|
||||
} while (ctx.Match(JsonToken.Comma));
|
||||
}
|
||||
|
||||
ctx.Consume(JsonToken.RightBrace, "Expected '}'");
|
||||
|
||||
var propertiesArray = properties?.Select(kv => new JsonObject.Property(kv.Key, kv.Value)).ToArray();
|
||||
var propertiesArray = properties?.Select(kv => new JsonPropertySyntax(kv.Key, kv.Value)).ToArray();
|
||||
|
||||
return new JsonObject(previous, propertiesArray ?? System.Array.Empty<JsonObject.Property>());
|
||||
return new JsonObjectSyntax(previous, propertiesArray ?? System.Array.Empty<JsonPropertySyntax>());
|
||||
}
|
||||
|
||||
private static ISyntaxNode Number(ParserContext<JsonToken> ctx)
|
||||
private static IJsonSyntax NumberExpression(ParserContext<JsonToken> ctx)
|
||||
{
|
||||
if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumber(ctx.Previous());
|
||||
if (ctx.Previous().Type != JsonToken.Minus) return new JsonNumberSyntax(ctx.Previous());
|
||||
|
||||
var minus = ctx.Previous();
|
||||
var number = ctx.Consume(JsonToken.Number, "Expected a number after '-'.");
|
||||
|
||||
return new JsonNumber(
|
||||
return new JsonNumberSyntax(
|
||||
new Lexer<JsonToken>.Token(minus.File, JsonToken.Number, $"-{number.Lexeme}", minus.Line, minus.Column)
|
||||
);
|
||||
}
|
||||
|
||||
private static ISyntaxNode String(ParserContext<JsonToken> ctx) => new JsonString(ctx.Previous());
|
||||
private static IJsonSyntax StringExpression(ParserContext<JsonToken> ctx) => new JsonStringSyntax(ctx.Previous());
|
||||
|
||||
private static ISyntaxNode Null(ParserContext<JsonToken> ctx) => new JsonNull(ctx.Previous());
|
||||
private static IJsonSyntax NullExpression(ParserContext<JsonToken> ctx) => new JsonNullSyntax(ctx.Previous());
|
||||
|
||||
private static ISyntaxNode Bool(ParserContext<JsonToken> ctx) => new JsonBool(ctx.Previous());
|
||||
private static IJsonSyntax BoolExpression(ParserContext<JsonToken> ctx) => new JsonBoolSyntax(ctx.Previous());
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser;
|
||||
|
||||
public readonly struct JsonString : ISyntaxNode
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public string Value => Token.Lexeme;
|
||||
|
||||
public JsonString(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
8
DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs
Normal file
8
DevDisciples.Json.Parser/Syntax/IJsonSyntax.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public interface IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
}
|
15
DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs
Normal file
15
DevDisciples.Json.Parser/Syntax/JsonArraySyntax.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonArraySyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public IJsonSyntax[] Elements { get; }
|
||||
|
||||
public JsonArraySyntax(Lexer<JsonToken>.Token token, IJsonSyntax[] elements)
|
||||
{
|
||||
Token = token;
|
||||
Elements = elements;
|
||||
}
|
||||
}
|
14
DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonBoolSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonBoolSyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public bool Value => bool.TryParse(Token.Lexeme, out var val) && val;
|
||||
|
||||
public JsonBoolSyntax(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
13
DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs
Normal file
13
DevDisciples.Json.Parser/Syntax/JsonNullSyntax.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonNullSyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
|
||||
public JsonNullSyntax(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
14
DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonNumberSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonNumberSyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public double Value => double.TryParse(Token.Lexeme.Replace('.', ','), out var val) ? val : default;
|
||||
|
||||
public JsonNumberSyntax(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
15
DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs
Normal file
15
DevDisciples.Json.Parser/Syntax/JsonObjectSyntax.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly partial struct JsonObjectSyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public JsonPropertySyntax[] Properties { get; }
|
||||
|
||||
public JsonObjectSyntax(Lexer<JsonToken>.Token token, JsonPropertySyntax[] properties)
|
||||
{
|
||||
Token = token;
|
||||
Properties = properties;
|
||||
}
|
||||
}
|
16
DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs
Normal file
16
DevDisciples.Json.Parser/Syntax/JsonPropertySyntax.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonPropertySyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token => Key;
|
||||
public Lexer<JsonToken>.Token Key { get; }
|
||||
public IJsonSyntax Value { get; }
|
||||
|
||||
public JsonPropertySyntax(Lexer<JsonToken>.Token key, IJsonSyntax value)
|
||||
{
|
||||
Key = key;
|
||||
Value = value;
|
||||
}
|
||||
}
|
14
DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs
Normal file
14
DevDisciples.Json.Parser/Syntax/JsonStringSyntax.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
public readonly struct JsonStringSyntax : IJsonSyntax
|
||||
{
|
||||
public Lexer<JsonToken>.Token Token { get; }
|
||||
public string Value => Token.Lexeme;
|
||||
|
||||
public JsonStringSyntax(Lexer<JsonToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
@ -40,4 +40,11 @@ public class TransformController : ControllerBase
|
||||
|
||||
return Ok(new { Result = result });
|
||||
}
|
||||
|
||||
[HttpPost("jsonpath")]
|
||||
public IActionResult JsonPath([FromBody] JsonPathRequest request)
|
||||
{
|
||||
var result = Tools.JsonPath.Interpreter.Evaluate(request.Source, request.Path);
|
||||
return Ok(new { Result = JsonFormatter.Format(result, new() { Beautify = true }) });
|
||||
}
|
||||
}
|
@ -2,6 +2,6 @@
|
||||
|
||||
public class Json2CsharpRequest : TransformRequest
|
||||
{
|
||||
public string RootClassName { get; } = Json2CSharpTranslator.Context.DefaultRootClassName;
|
||||
public string Namespace { get; } = Json2CSharpTranslator.Context.DefaultNamespace;
|
||||
public string RootClassName { get; init; } = Json2CSharpTranslator.Context.DefaultRootClassName;
|
||||
public string Namespace { get; init; } = Json2CSharpTranslator.Context.DefaultNamespace;
|
||||
}
|
6
DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs
Normal file
6
DevDisciples.Json.Tools.API/Requests/JsonPathRequest.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace DevDisciples.Json.Tools.API.Requests;
|
||||
|
||||
public class JsonPathRequest : TransformRequest
|
||||
{
|
||||
public string Path { get; init; } = string.Empty;
|
||||
}
|
@ -2,5 +2,5 @@
|
||||
|
||||
public class PrettifyRequest : TransformRequest
|
||||
{
|
||||
public int IndentSize { get; } = JsonFormatter.Context.DefaultIndentSize;
|
||||
public int IndentSize { get; init; } = JsonFormatter.Context.DefaultIndentSize;
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import {AppComponent} from "./app.component";
|
||||
import {HomeComponent} from "./home/home.component";
|
||||
import {PrettifyComponent} from "./prettify/prettify.component";
|
||||
import {UglifyComponent} from "./uglify/uglify.component";
|
||||
import {Json2CsharpComponent} from "./json2csharp/json2-csharp.component";
|
||||
import {JsonPathComponent} from "./json-path/json-path.component";
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: 'home', component: HomeComponent },
|
||||
{ path: 'prettify', component: PrettifyComponent },
|
||||
{ path: 'uglify', component: UglifyComponent },
|
||||
{ path: 'json2csharp', component: Json2CsharpComponent },
|
||||
{ path: 'jsonpath', component: JsonPathComponent },
|
||||
];
|
||||
|
@ -1,26 +1,26 @@
|
||||
export const MonacoJsonConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'json',
|
||||
readOnly: false,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const ReadOnlyMonacoJsonConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'json',
|
||||
readOnly: true,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const ReadOnlyMonacoCSharpConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'csharp',
|
||||
readOnly: false,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const GenerateDefaultJsonObjectString = (space: number = 0): string => {
|
||||
return JSON.stringify({first_name: "John", last_name: "Doe"}, null, space);
|
||||
}
|
||||
|
||||
export const DebounceTime: number = 750;
|
||||
export const MonacoJsonConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'json',
|
||||
readOnly: false,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const ReadOnlyMonacoJsonConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'json',
|
||||
readOnly: true,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const ReadOnlyMonacoCSharpConfig = {
|
||||
theme: 'vs-dark',
|
||||
language: 'csharp',
|
||||
readOnly: false,
|
||||
automaticLayout: true
|
||||
};
|
||||
|
||||
export const GenerateDefaultJsonObjectString = (space: number = 0): string => {
|
||||
return JSON.stringify({first_name: "John", last_name: "Doe"}, null, space);
|
||||
}
|
||||
|
||||
export const DebounceTime: number = 750;
|
||||
|
@ -0,0 +1,13 @@
|
||||
<h1>JSON to C#</h1>
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Path</mat-label>
|
||||
<input matInput placeholder="$.*" [value]="$path.value" (input)="handlePathChange($event)">
|
||||
</mat-form-field>
|
||||
|
||||
<app-input-output [input]="$input.value"
|
||||
[inputOptions]="inputOptions"
|
||||
(onInputChange)="handleInputChange($event)"
|
||||
[output]="output"
|
||||
[outputOptions]="outputOptions">
|
||||
</app-input-output>
|
@ -0,0 +1,3 @@
|
||||
mat-form-field {
|
||||
width: 100%;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { JsonPathComponent } from './json-path.component';
|
||||
|
||||
describe('JsonPathComponent', () => {
|
||||
let component: JsonPathComponent;
|
||||
let fixture: ComponentFixture<JsonPathComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [JsonPathComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(JsonPathComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {InputOutputComponent} from "../input-output/input-output.component";
|
||||
import {DebounceTime, GenerateDefaultJsonObjectString, MonacoJsonConfig, ReadOnlyMonacoCSharpConfig} from "../defaults";
|
||||
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||
import {JsonTransformService} from "../json-transform.service";
|
||||
import {MatFormField} from "@angular/material/form-field";
|
||||
import {MatInputModule} from "@angular/material/input";
|
||||
|
||||
@Component({
|
||||
selector: 'app-json-path',
|
||||
standalone: true,
|
||||
imports: [
|
||||
InputOutputComponent,
|
||||
MatFormField,
|
||||
MatInputModule
|
||||
],
|
||||
templateUrl: './json-path.component.html',
|
||||
styleUrl: './json-path.component.scss'
|
||||
})
|
||||
export class JsonPathComponent implements OnInit {
|
||||
$path: BehaviorSubject<string> = new BehaviorSubject<string>("$.*");
|
||||
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||
inputOptions = MonacoJsonConfig;
|
||||
output: string = "";
|
||||
outputOptions = ReadOnlyMonacoCSharpConfig;
|
||||
|
||||
constructor(private service: JsonTransformService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.$input
|
||||
.pipe(debounceTime(DebounceTime))
|
||||
.subscribe(input => this.update(input, this.$path.value));
|
||||
|
||||
this.$path
|
||||
.pipe(debounceTime(DebounceTime))
|
||||
.subscribe(path => this.update(this.$input.value, path));
|
||||
|
||||
this.update(this.$input.value, this.$path.value);
|
||||
}
|
||||
|
||||
update(input: string, path: string): void {
|
||||
this.service
|
||||
.jsonPath(input, path)
|
||||
.subscribe({
|
||||
next: response => {
|
||||
console.log(response);
|
||||
this.output = response.body.result;
|
||||
},
|
||||
error: response => {
|
||||
console.log(response)
|
||||
if (response.status === 499) {
|
||||
this.output = response.error.detail;
|
||||
console.log(response.error.detail);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handlePathChange($event: any): void {
|
||||
console.log($event);
|
||||
this.$path.next($event.target.value);
|
||||
}
|
||||
|
||||
handleInputChange($event: any): void {
|
||||
console.log($event);
|
||||
this.$input.next($event);
|
||||
}
|
||||
}
|
@ -22,4 +22,8 @@ export class JsonTransformService {
|
||||
public json2csharp(source: string): Observable<HttpResponse<any>> {
|
||||
return this.http.post(`${this.url}/api/transform/json2csharp`, {Source: source}, {observe: "response"});
|
||||
}
|
||||
|
||||
public jsonPath(source: string, path: string): Observable<HttpResponse<any>> {
|
||||
return this.http.post(`${this.url}/api/transform/jsonpath`, {source: source, path: path}, {observe: "response"});
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h1>JSON to C#</h1>
|
||||
|
||||
<app-input-output [input]="input"
|
||||
<app-input-output [input]="$input.value"
|
||||
[inputOptions]="inputOptions"
|
||||
(onInputChange)="handleInputChange($event)"
|
||||
[output]="output"
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
MonacoJsonConfig,
|
||||
ReadOnlyMonacoCSharpConfig
|
||||
} from "../defaults";
|
||||
import {debounceTime, Subject} from "rxjs";
|
||||
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-json2csharp',
|
||||
@ -19,8 +19,7 @@ import {debounceTime, Subject} from "rxjs";
|
||||
styleUrl: './json2-csharp.component.scss'
|
||||
})
|
||||
export class Json2CsharpComponent implements OnInit {
|
||||
input: string = GenerateDefaultJsonObjectString(2);
|
||||
$input: Subject<string> = new Subject<string>();
|
||||
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||
inputOptions = MonacoJsonConfig;
|
||||
output: string = "";
|
||||
outputOptions = ReadOnlyMonacoCSharpConfig;
|
||||
@ -33,7 +32,7 @@ export class Json2CsharpComponent implements OnInit {
|
||||
.pipe(debounceTime(DebounceTime))
|
||||
.subscribe(input => this.update(input));
|
||||
|
||||
this.update(this.input);
|
||||
this.update(this.$input.value);
|
||||
}
|
||||
|
||||
update(input: string): void {
|
||||
|
@ -6,6 +6,7 @@
|
||||
<mat-list-item routerLink="/prettify">Prettify</mat-list-item>
|
||||
<mat-list-item routerLink="/uglify">Uglify</mat-list-item>
|
||||
<mat-list-item routerLink="/json2csharp">JSON to C#</mat-list-item>
|
||||
<mat-list-item routerLink="/jsonpath">JSON Path</mat-list-item>
|
||||
</mat-nav-list>
|
||||
</mat-sidenav>
|
||||
|
||||
|
@ -12,4 +12,5 @@ mat-sidenav {
|
||||
|
||||
main {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h1>JSON Prettify</h1>
|
||||
|
||||
<app-input-output [input]="input"
|
||||
<app-input-output [input]="$input.value"
|
||||
[inputOptions]="inputOptions"
|
||||
(onInputChange)="handleInputChange($event)"
|
||||
[output]="output"
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
MonacoJsonConfig,
|
||||
ReadOnlyMonacoJsonConfig
|
||||
} from "../defaults";
|
||||
import {debounceTime, Subject} from "rxjs";
|
||||
import {BehaviorSubject, debounceTime} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-prettify',
|
||||
@ -23,12 +23,12 @@ import {debounceTime, Subject} from "rxjs";
|
||||
styleUrl: './prettify.component.scss'
|
||||
})
|
||||
export class PrettifyComponent implements OnInit {
|
||||
input: string = GenerateDefaultJsonObjectString();
|
||||
$input: Subject<string> = new Subject<string>();
|
||||
// input: string = ;
|
||||
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString());
|
||||
inputOptions = MonacoJsonConfig;
|
||||
output: string = GenerateDefaultJsonObjectString(2);
|
||||
outputOptions = ReadOnlyMonacoJsonConfig;
|
||||
error: string = "";
|
||||
// error: string = "";
|
||||
|
||||
constructor(private service: JsonTransformService) {
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
<h1>JSON Uglify</h1>
|
||||
|
||||
<app-input-output [input]="input"
|
||||
<app-input-output [input]="$input.value"
|
||||
[inputOptions]="inputOptions"
|
||||
(onInputChange)="handleInputChange($event)"
|
||||
[output]="output"
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
MonacoJsonConfig,
|
||||
ReadOnlyMonacoJsonConfig
|
||||
} from "../defaults";
|
||||
import {debounceTime, Subject} from "rxjs";
|
||||
import {debounceTime, BehaviorSubject} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-uglify',
|
||||
@ -19,8 +19,8 @@ import {debounceTime, Subject} from "rxjs";
|
||||
styleUrl: './uglify.component.scss'
|
||||
})
|
||||
export class UglifyComponent implements OnInit {
|
||||
input: string = GenerateDefaultJsonObjectString(2);
|
||||
$input: Subject<string> = new Subject<string>();
|
||||
// input: string = ;
|
||||
$input: BehaviorSubject<string> = new BehaviorSubject<string>(GenerateDefaultJsonObjectString(2));
|
||||
inputOptions = MonacoJsonConfig;
|
||||
output: string = GenerateDefaultJsonObjectString();
|
||||
outputOptions = ReadOnlyMonacoJsonConfig;
|
||||
|
@ -8,9 +8,11 @@ public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public const string DefaultRootClassName = "Root";
|
||||
public const string DefaultNamespace = "My.Namespace";
|
||||
|
||||
|
||||
public string RootClassName { get; init; } = DefaultRootClassName;
|
||||
public string Namespace { get; init; } = DefaultNamespace;
|
||||
public string CurrentName { get; set; } = string.Empty;
|
||||
|
||||
public List<ClassTranslation> Classes { get; } = new();
|
||||
public readonly StringBuilder Builder = new();
|
||||
}
|
||||
|
@ -1,63 +0,0 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Parsing;
|
||||
using Humanizer;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonArrayTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var name = NameFromArgs(args);
|
||||
var array = (JsonArray)visitee;
|
||||
var type = "object";
|
||||
|
||||
if (array.Elements.All(e => e is JsonObject))
|
||||
{
|
||||
context.Classes.Add(SquashObjects(name, array.Elements, args));
|
||||
type = context.Classes.Last().Name;
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonString es && DateTime.TryParse(es.Value, out _)))
|
||||
{
|
||||
type = "DateTime";
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonString))
|
||||
{
|
||||
type = "string";
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonNumber))
|
||||
{
|
||||
type = array.Elements.Any(e => ((JsonNumber)e).Token.Lexeme.Contains('.')) ? "double" : "int";
|
||||
}
|
||||
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = $"List<{type}>",
|
||||
Name = name,
|
||||
};
|
||||
}
|
||||
|
||||
private static ClassTranslation SquashObjects(string className, IEnumerable<ISyntaxNode> objects, object[] args)
|
||||
{
|
||||
var classes = objects
|
||||
.Select(@object => JsonObjectTranslator.Translate(@object, args))
|
||||
.ToArray();
|
||||
|
||||
var squashed = new ClassTranslation
|
||||
{
|
||||
Name = className.Singularize(),
|
||||
Properties = new()
|
||||
};
|
||||
|
||||
foreach (var @class in classes)
|
||||
foreach (var prop in ((ClassTranslation)@class).Properties)
|
||||
if (squashed.Properties.All(p => p.Name != prop.Name))
|
||||
squashed.Properties.Add(prop);
|
||||
|
||||
return squashed;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonBoolTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = "bool",
|
||||
Name = NameFromArgs(args),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonNullTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = "object",
|
||||
Name = NameFromArgs(args),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonNumberTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = ((JsonNumber)visitee).Token.Lexeme.Contains('.') ? "double" : "int",
|
||||
Name = NameFromArgs(args),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonObjectTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var name = NameFromArgs(args);
|
||||
var @class = new ClassTranslation { Name = name, Properties = new() };
|
||||
var @object = (JsonObject)visitee;
|
||||
|
||||
context.Classes.Add(@class);
|
||||
|
||||
foreach (var prop in @object.Properties)
|
||||
{
|
||||
var visitor = Visitors[prop.Value.GetType()];
|
||||
var translation = visitor(prop.Value, context, prop.Key);
|
||||
|
||||
switch (translation)
|
||||
{
|
||||
case ClassTranslation:
|
||||
@class.Properties.Add(new PropertyTranslation
|
||||
{
|
||||
Type = translation.Name,
|
||||
Name = translation.Name,
|
||||
});
|
||||
break;
|
||||
case PropertyTranslation property:
|
||||
@class.Properties.Add(property);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return @class;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static class JsonStringTranslator
|
||||
{
|
||||
public static ITranslation Translate(ISyntaxNode visitee, object[] args)
|
||||
{
|
||||
var @string = (JsonString)visitee;
|
||||
var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string";
|
||||
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = type,
|
||||
Name = NameFromArgs(args),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Json.Parser.Syntax;
|
||||
using DevDisciples.Parsing;
|
||||
using Humanizer;
|
||||
|
||||
@ -6,28 +7,28 @@ namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class Json2CSharpTranslator
|
||||
{
|
||||
public static readonly VisitorContainer<ISyntaxNode, ITranslation> Visitors;
|
||||
private static readonly VisitorContainer<IJsonSyntax, Context, ITranslation> Visitors = new();
|
||||
|
||||
static Json2CSharpTranslator()
|
||||
{
|
||||
Visitors = new();
|
||||
Visitors.Register<JsonObject>(JsonObjectTranslator.Translate);
|
||||
Visitors.Register<JsonArray>(JsonArrayTranslator.Translate);
|
||||
Visitors.Register<JsonString>(JsonStringTranslator.Translate);
|
||||
Visitors.Register<JsonNumber>(JsonNumberTranslator.Translate);
|
||||
Visitors.Register<JsonBool>(JsonBoolTranslator.Translate);
|
||||
Visitors.Register<JsonNull>(JsonNullTranslator.Translate);
|
||||
Visitors.Register<JsonObjectSyntax>(Object);
|
||||
Visitors.Register<JsonArraySyntax>(Array);
|
||||
Visitors.Register<JsonStringSyntax>(String);
|
||||
Visitors.Register<JsonNumberSyntax>(Number);
|
||||
Visitors.Register<JsonBoolSyntax>(Bool);
|
||||
Visitors.Register<JsonNullSyntax>(Null);
|
||||
}
|
||||
|
||||
public static string Translate(string source, Context? context = null)
|
||||
{
|
||||
if (JsonParser.Parse("<source>", source) is not JsonObject root)
|
||||
if (JsonParser.Parse("<source>", source) is not JsonObjectSyntax root)
|
||||
throw new ParsingException("Expected a JSON object.");
|
||||
|
||||
context ??= new();
|
||||
context.CurrentName = context.RootClassName;
|
||||
|
||||
var visitor = Visitors[typeof(JsonObject)];
|
||||
visitor(root, context, context.RootClassName);
|
||||
var visitor = Visitors[typeof(JsonObjectSyntax)];
|
||||
visitor(root, context);
|
||||
|
||||
context.Builder.Append("//using System;\n");
|
||||
context.Builder.Append("//using System.Collections.Generic;\n");
|
||||
@ -38,7 +39,127 @@ public static partial class Json2CSharpTranslator
|
||||
return context.Builder.ToString();
|
||||
}
|
||||
|
||||
private static Context ContextFromArgs(object[] args) => (args[0] as Context)!;
|
||||
private static ITranslation Object(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var @object = (JsonObjectSyntax)visitee;
|
||||
var @class = new ClassTranslation
|
||||
{
|
||||
Name = context.CurrentName,
|
||||
Properties = new()
|
||||
};
|
||||
|
||||
private static string NameFromArgs(object[] args) => ((string)args[1]).Camelize();
|
||||
context.Classes.Add(@class);
|
||||
|
||||
foreach (var prop in @object.Properties)
|
||||
{
|
||||
context.CurrentName = prop.Key.Lexeme;
|
||||
var visitor = Visitors[prop.Value.GetType()];
|
||||
var translation = visitor(prop.Value, context);
|
||||
|
||||
switch (translation)
|
||||
{
|
||||
case ClassTranslation:
|
||||
@class.Properties.Add(new PropertyTranslation
|
||||
{
|
||||
Type = translation.Name,
|
||||
Name = translation.Name,
|
||||
});
|
||||
break;
|
||||
case PropertyTranslation property:
|
||||
@class.Properties.Add(property);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return @class;
|
||||
}
|
||||
|
||||
private static ITranslation Array(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var array = (JsonArraySyntax)visitee;
|
||||
var type = "object";
|
||||
|
||||
if (array.Elements.All(e => e is JsonObjectSyntax))
|
||||
{
|
||||
context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context));
|
||||
type = context.Classes.Last().Name;
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonStringSyntax es && DateTime.TryParse(es.Value, out _)))
|
||||
{
|
||||
type = "DateTime";
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonStringSyntax))
|
||||
{
|
||||
type = "string";
|
||||
}
|
||||
else if (array.Elements.All(e => e is JsonNumberSyntax))
|
||||
{
|
||||
type = array.Elements.Any(e => ((JsonNumberSyntax)e).Token.Lexeme.Contains('.')) ? "double" : "int";
|
||||
}
|
||||
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = $"List<{type}>",
|
||||
Name = context.CurrentName,
|
||||
};
|
||||
}
|
||||
|
||||
private static ITranslation String(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var @string = (JsonStringSyntax)visitee;
|
||||
var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string";
|
||||
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = type,
|
||||
Name = context.CurrentName,
|
||||
};
|
||||
}
|
||||
|
||||
private static ITranslation Number(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = ((JsonNumberSyntax)visitee).Token.Lexeme.Contains('.') ? "double" : "int",
|
||||
Name = context.CurrentName,
|
||||
};
|
||||
}
|
||||
|
||||
private static ITranslation Bool(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = "bool",
|
||||
Name = context.CurrentName,
|
||||
};
|
||||
}
|
||||
|
||||
private static ITranslation Null(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
return new PropertyTranslation
|
||||
{
|
||||
Type = "object",
|
||||
Name = context.CurrentName,
|
||||
};
|
||||
}
|
||||
|
||||
private static ClassTranslation SquashObjects(string className, IEnumerable<IJsonSyntax> objects, Context context)
|
||||
{
|
||||
var classes = objects
|
||||
.Select(@object => Object(@object, context))
|
||||
.ToArray();
|
||||
|
||||
var squashed = new ClassTranslation
|
||||
{
|
||||
Name = className.Singularize(),
|
||||
Properties = new()
|
||||
};
|
||||
|
||||
foreach (var @class in classes)
|
||||
foreach (var prop in ((ClassTranslation)@class).Properties)
|
||||
if (squashed.Properties.All(p => p.Name != prop.Name))
|
||||
squashed.Properties.Add(prop);
|
||||
|
||||
return squashed;
|
||||
}
|
||||
}
|
@ -1,45 +1,39 @@
|
||||
using DevDisciples.Json.Parser;
|
||||
using DevDisciples.Json.Parser.Syntax;
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class JsonFormatter
|
||||
{
|
||||
public static VisitorContainer Visitors { get; }
|
||||
private static VisitorContainer<IJsonSyntax, Context> Visitors { get; } = new();
|
||||
|
||||
static JsonFormatter()
|
||||
{
|
||||
Visitors = new();
|
||||
Visitors.Register<JsonArray>(PrintArray);
|
||||
Visitors.Register<JsonObject>(PrintObject);
|
||||
Visitors.Register<JsonString>(PrintString);
|
||||
Visitors.Register<JsonNumber>(PrintNumber);
|
||||
Visitors.Register<JsonBool>(PrintBool);
|
||||
Visitors.Register<JsonNull>(PrintNull);
|
||||
Visitors.Register<JsonArraySyntax>(PrintArray);
|
||||
Visitors.Register<JsonObjectSyntax>(PrintObject);
|
||||
Visitors.Register<JsonStringSyntax>(PrintString);
|
||||
Visitors.Register<JsonNumberSyntax>(PrintNumber);
|
||||
Visitors.Register<JsonBoolSyntax>(PrintBool);
|
||||
Visitors.Register<JsonNullSyntax>(PrintNull);
|
||||
}
|
||||
|
||||
public static string Format(string source, Context? context)
|
||||
{
|
||||
var nodes = JsonParser.Parse("<source>", source);
|
||||
return Format(nodes, context);
|
||||
var node = JsonParser.Parse("<source>", source);
|
||||
return Format(node, context);
|
||||
}
|
||||
|
||||
private static string Format(ISyntaxNode visitee, Context? context = null)
|
||||
public static string Format(IJsonSyntax visitee, Context? context = null)
|
||||
{
|
||||
var ctx = context ?? new();
|
||||
Visitors[visitee.GetType()](visitee, ctx);
|
||||
return context!.Builder.ToString();
|
||||
context ??= new();
|
||||
Visitors[visitee.GetType()](visitee, context);
|
||||
return context.Builder.ToString();
|
||||
}
|
||||
|
||||
private static Context ContextFromArgs(object[] args)
|
||||
private static void PrintArray(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
return (args[0] as Context)!;
|
||||
}
|
||||
|
||||
private static void PrintArray(object visitee, object[] args)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var array = (JsonArray)visitee;
|
||||
var array = (JsonArraySyntax)visitee;
|
||||
|
||||
context.Builder.Append($"[{context.NewLine}");
|
||||
context.IncrementDepth();
|
||||
@ -48,7 +42,7 @@ public static partial class JsonFormatter
|
||||
{
|
||||
var node = array.Elements[i];
|
||||
context.Builder.Append(context.Indent);
|
||||
Visitors[node.GetType()](node, args);
|
||||
Visitors[node.GetType()](node, context);
|
||||
if (i < array.Elements.Length - 1) context.Builder.Append($",{context.NewLine}");
|
||||
}
|
||||
|
||||
@ -56,10 +50,9 @@ public static partial class JsonFormatter
|
||||
context.Builder.Append($"{context.NewLine}{context.Indent}]");
|
||||
}
|
||||
|
||||
private static void PrintObject(object visitee, object[] args)
|
||||
private static void PrintObject(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var @object = (JsonObject)visitee;
|
||||
var @object = (JsonObjectSyntax)visitee;
|
||||
|
||||
context.Builder.Append($"{{{context.NewLine}");
|
||||
context.IncrementDepth();
|
||||
@ -68,8 +61,8 @@ public static partial class JsonFormatter
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var property = @object.Properties.ElementAt(i);
|
||||
context.Builder.Append($"{context.Indent}\"{property.Key}\":{context.Space}");
|
||||
Visitors[property.Value.GetType()](property.Value, args);
|
||||
context.Builder.Append($"{context.Indent}\"{property.Key.Lexeme}\":{context.Space}");
|
||||
Visitors[property.Value.GetType()](property.Value, context);
|
||||
if (i < count - 1) context.Builder.Append($",{context.NewLine}");
|
||||
}
|
||||
|
||||
@ -77,30 +70,26 @@ public static partial class JsonFormatter
|
||||
context.Builder.Append($"{context.NewLine}{context.Indent}}}");
|
||||
}
|
||||
|
||||
private static void PrintString(object visitee, object[] args)
|
||||
private static void PrintString(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var @string = (JsonString)visitee;
|
||||
var @string = (JsonStringSyntax)visitee;
|
||||
context.Builder.Append($"\"{@string.Token.Lexeme}\"");
|
||||
}
|
||||
|
||||
private static void PrintNumber(object visitee, object[] args)
|
||||
private static void PrintNumber(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var number = (JsonNumber)visitee;
|
||||
var number = (JsonNumberSyntax)visitee;
|
||||
context.Builder.Append($"{number.Value}");
|
||||
}
|
||||
|
||||
private static void PrintBool(object visitee, object[] args)
|
||||
private static void PrintBool(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
var @bool = (JsonBool)visitee;
|
||||
var @bool = (JsonBoolSyntax)visitee;
|
||||
context.Builder.Append($"{@bool.Value.ToString().ToLower()}");
|
||||
}
|
||||
|
||||
private static void PrintNull(object visitee, object[] args)
|
||||
private static void PrintNull(IJsonSyntax visitee, Context context)
|
||||
{
|
||||
var context = ContextFromArgs(args);
|
||||
context.Builder.Append("null");
|
||||
}
|
||||
}
|
14
DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs
Normal file
14
DevDisciples.Json.Tools/JsonPath.Interpreter.Context.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using DevDisciples.Json.Parser.Syntax;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class JsonPath
|
||||
{
|
||||
public static partial class Interpreter
|
||||
{
|
||||
public class Context
|
||||
{
|
||||
public IJsonSyntax Target { get; set; } = default!;
|
||||
}
|
||||
}
|
||||
}
|
167
DevDisciples.Json.Tools/JsonPath.Interpreter.cs
Normal file
167
DevDisciples.Json.Tools/JsonPath.Interpreter.cs
Normal file
@ -0,0 +1,167 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
38
DevDisciples.Json.Tools/JsonPath.Lexer.cs
Normal file
38
DevDisciples.Json.Tools/JsonPath.Lexer.cs
Normal file
@ -0,0 +1,38 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class JsonPath
|
||||
{
|
||||
public class Lexer : Lexer<JsonPathToken>
|
||||
{
|
||||
public static readonly Lexer Default = new();
|
||||
|
||||
protected override JsonPathToken EndOfSource => JsonPathToken.EndOfSource;
|
||||
|
||||
public Lexer()
|
||||
{
|
||||
Rules =
|
||||
[
|
||||
DefaultRule.NewLine,
|
||||
DefaultRule.Number(JsonPathToken.Number),
|
||||
DefaultRule.Identifier(JsonPathToken.Identifier),
|
||||
DefaultRule.DoubleQuoteString(JsonPathToken.String),
|
||||
|
||||
ctx => DefaultRule.IgnoreWhitespace(ctx),
|
||||
ctx => Match(ctx, JsonPathToken.False, "false"),
|
||||
ctx => Match(ctx, JsonPathToken.True, "true"),
|
||||
ctx => Match(ctx, JsonPathToken.Null, "null"),
|
||||
ctx => Match(ctx, JsonPathToken.DotDot, ".."),
|
||||
ctx => Match(ctx, JsonPathToken.DollarSign, '$'),
|
||||
ctx => Match(ctx, JsonPathToken.Asterisk, '*'),
|
||||
ctx => Match(ctx, JsonPathToken.Dot, '.'),
|
||||
ctx => Match(ctx, JsonPathToken.Minus, '-'),
|
||||
ctx => Match(ctx, JsonPathToken.Colon, ':'),
|
||||
ctx => Match(ctx, JsonPathToken.LeftBracket, '['),
|
||||
ctx => Match(ctx, JsonPathToken.Comma, ','),
|
||||
ctx => Match(ctx, JsonPathToken.RightBracket, ']'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
16
DevDisciples.Json.Tools/JsonPath.Parser.Context.cs
Normal file
16
DevDisciples.Json.Tools/JsonPath.Parser.Context.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class JsonPath
|
||||
{
|
||||
public static partial class Parser
|
||||
{
|
||||
public class Context : ParserContext<JsonPathToken>
|
||||
{
|
||||
public Context(Memory<Lexer<JsonPathToken>.Token> tokens) : base(tokens, JsonPathToken.EndOfSource)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
110
DevDisciples.Json.Tools/JsonPath.Parser.cs
Normal file
110
DevDisciples.Json.Tools/JsonPath.Parser.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public static partial class JsonPath
|
||||
{
|
||||
public static partial class Parser
|
||||
{
|
||||
public static List<IJsonPathSyntax> Parse(string path)
|
||||
{
|
||||
var pathTokens = Lexer.Default.Lex("<path>", path);
|
||||
var context = new Context(pathTokens.ToArray());
|
||||
|
||||
context.Match(JsonPathToken.DollarSign); // Match the '$' eagerly. This will make it optional by default.
|
||||
|
||||
var syntax = new List<IJsonPathSyntax>();
|
||||
while (!context.Ended())
|
||||
syntax.Add(Expression(context));
|
||||
|
||||
return syntax;
|
||||
}
|
||||
|
||||
private static IJsonPathSyntax Expression(Context context)
|
||||
{
|
||||
if (context.Match(JsonPathToken.Dot))
|
||||
return PropertyAccessorExpression(context);
|
||||
|
||||
if (context.Match(JsonPathToken.LeftBracket))
|
||||
return IndexAccessorExpression(context);
|
||||
|
||||
throw context.Error("Invalid expression.");
|
||||
}
|
||||
|
||||
private static IJsonPathSyntax PropertyAccessorExpression(Context context)
|
||||
{
|
||||
var token = context.Previous();
|
||||
|
||||
IJsonPathSyntax getter;
|
||||
|
||||
if (context.Match(JsonPathToken.Asterisk))
|
||||
getter = new WildCardSyntax(context.Previous());
|
||||
|
||||
else if (context.Match(JsonPathToken.Identifier))
|
||||
getter = new PropertySyntax(context.Previous());
|
||||
|
||||
else throw context.Error("Expected a getter expression");
|
||||
|
||||
return new PropertyAccessorSyntax(token, getter);
|
||||
}
|
||||
|
||||
private static IJsonPathSyntax IndexAccessorExpression(Context context)
|
||||
{
|
||||
var token = context.Previous();
|
||||
|
||||
IJsonPathSyntax syntax;
|
||||
|
||||
if (context.Match(JsonPathToken.Asterisk))
|
||||
syntax = new WildCardSyntax(context.Previous());
|
||||
|
||||
else if (context.Match(JsonPathToken.Number))
|
||||
syntax = ArrayIndexExpression(token, context);
|
||||
|
||||
else if (context.Match(JsonPathToken.String))
|
||||
syntax = ObjectIndexExpression(token, context);
|
||||
|
||||
else throw context.Error("Expected an index expression.");
|
||||
|
||||
context.Consume(JsonPathToken.RightBracket, "Expected ']' after index expression.");
|
||||
|
||||
return syntax;
|
||||
}
|
||||
|
||||
private static IJsonPathSyntax ArrayIndexExpression(Lexer<JsonPathToken>.Token token, Context context)
|
||||
{
|
||||
var index = context.Previous();
|
||||
|
||||
if (!context.Match(JsonPathToken.Comma)) return new ArrayIndexSyntax(token, index);
|
||||
|
||||
var indexes = new List<Lexer<JsonPathToken>.Token> { index };
|
||||
|
||||
do
|
||||
{
|
||||
index = context.Consume(JsonPathToken.Number, "Invalid array index.");
|
||||
|
||||
if (!int.TryParse(index.Lexeme, out _)) throw context.Error(index, "Invalid array index.");
|
||||
|
||||
indexes.Add(index);
|
||||
} while (!context.Ended() && context.Match(JsonPathToken.Comma));
|
||||
|
||||
return new ArrayIndexListSyntax(token, indexes.ToArray());
|
||||
}
|
||||
|
||||
private static IJsonPathSyntax ObjectIndexExpression(Lexer<JsonPathToken>.Token token, Context context)
|
||||
{
|
||||
var index = context.Previous();
|
||||
|
||||
if (!context.Match(JsonPathToken.Comma)) return new ObjectIndexSyntax(token, index);
|
||||
|
||||
var indexes = new List<Lexer<JsonPathToken>.Token> { index };
|
||||
|
||||
do
|
||||
{
|
||||
index = context.Consume(JsonPathToken.String, "Invalid object index.");
|
||||
indexes.Add(index);
|
||||
} while (!context.Ended() && context.Match(JsonPathToken.Comma));
|
||||
|
||||
return new ObjectIndexListSyntax(token, indexes.ToArray());
|
||||
}
|
||||
}
|
||||
}
|
96
DevDisciples.Json.Tools/JsonPath.Syntax.cs
Normal file
96
DevDisciples.Json.Tools/JsonPath.Syntax.cs
Normal file
@ -0,0 +1,96 @@
|
||||
using DevDisciples.Parsing;
|
||||
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
// See https://docs.hevodata.com/sources/engg-analytics/streaming/rest-api/writing-jsonpath-expressions/
|
||||
|
||||
public static partial class JsonPath
|
||||
{
|
||||
|
||||
public interface IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
}
|
||||
|
||||
public readonly struct WildCardSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
|
||||
public WildCardSyntax(Lexer<JsonPathToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ArrayIndexSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
public Lexer<JsonPathToken>.Token Index { get; }
|
||||
public int IndexAsInt => int.Parse(Token.Lexeme);
|
||||
|
||||
public ArrayIndexSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token index)
|
||||
{
|
||||
Token = token;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ArrayIndexListSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
public Lexer<JsonPathToken>.Token[] Indices { get; }
|
||||
public int ValueAt(int index) => int.Parse(Indices[index].Lexeme);
|
||||
|
||||
public ArrayIndexListSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token[] indices)
|
||||
{
|
||||
Token = token;
|
||||
Indices = indices;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ObjectIndexSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
public Lexer<JsonPathToken>.Token Index { get; }
|
||||
|
||||
public ObjectIndexSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token index)
|
||||
{
|
||||
Token = token;
|
||||
Index = index;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ObjectIndexListSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
public Lexer<JsonPathToken>.Token[] Indexes { get; }
|
||||
|
||||
public ObjectIndexListSyntax(Lexer<JsonPathToken>.Token token, Lexer<JsonPathToken>.Token[] indexes)
|
||||
{
|
||||
Token = token;
|
||||
Indexes = indexes;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct PropertyAccessorSyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
public IJsonPathSyntax Getter { get; }
|
||||
|
||||
public PropertyAccessorSyntax(Lexer<JsonPathToken>.Token token, IJsonPathSyntax getter)
|
||||
{
|
||||
Token = token;
|
||||
Getter = getter;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct PropertySyntax : IJsonPathSyntax
|
||||
{
|
||||
public Lexer<JsonPathToken>.Token Token { get; }
|
||||
|
||||
public PropertySyntax(Lexer<JsonPathToken>.Token token)
|
||||
{
|
||||
Token = token;
|
||||
}
|
||||
}
|
||||
}
|
21
DevDisciples.Json.Tools/JsonPathToken.cs
Normal file
21
DevDisciples.Json.Tools/JsonPathToken.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace DevDisciples.Json.Tools;
|
||||
|
||||
public enum JsonPathToken
|
||||
{
|
||||
DollarSign,
|
||||
Asterisk,
|
||||
Dot,
|
||||
DotDot,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
Identifier,
|
||||
String,
|
||||
Minus,
|
||||
Number,
|
||||
Comma,
|
||||
Colon,
|
||||
Null,
|
||||
True,
|
||||
False,
|
||||
EndOfSource,
|
||||
}
|
10
DevDisciples.Parsing/Extensions/CastingExtensions.cs
Normal file
10
DevDisciples.Parsing/Extensions/CastingExtensions.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace DevDisciples.Parsing.Extensions;
|
||||
|
||||
public static class CastingExtensions
|
||||
{
|
||||
public static T As<T>(this object @object)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(@object);
|
||||
return (T)@object;
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
namespace DevDisciples.Parsing;
|
||||
|
||||
public interface ISyntaxNode { }
|
@ -187,10 +187,8 @@ public abstract partial class Lexer<TToken> where TToken : Enum
|
||||
|
||||
ProcessDigits(src);
|
||||
|
||||
if (src.Peek() == '.' && IsDigit(src.Peek(1)))
|
||||
if (IsDigit(src.Peek(1)) && src.Match('.'))
|
||||
{
|
||||
// Consume the "."
|
||||
src.Advance();
|
||||
ProcessDigits(src);
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,11 @@ public abstract class ParsableStream<T>
|
||||
|
||||
protected ReadOnlySpan<T> Tokens => _tokens.Span;
|
||||
|
||||
public int Position { get; set; }
|
||||
protected int Position { get; set; }
|
||||
|
||||
public T Current => Position < Tokens.Length ? Tokens[Position] : default!;
|
||||
|
||||
public ParsableStream(ReadOnlyMemory<T> tokens)
|
||||
protected ParsableStream(ReadOnlyMemory<T> tokens)
|
||||
{
|
||||
_tokens = tokens;
|
||||
}
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where TToken : Enum
|
||||
{
|
||||
protected readonly TToken _endOfSource;
|
||||
private readonly TToken _endOfSource;
|
||||
|
||||
public ParserContext(Memory<Lexer<TToken>.Token> tokens, TToken endOfSource) : base(tokens)
|
||||
protected ParserContext(Memory<Lexer<TToken>.Token> tokens, TToken endOfSource) : base(tokens)
|
||||
{
|
||||
_endOfSource = endOfSource;
|
||||
}
|
||||
@ -98,9 +98,4 @@ public class ParserContext<TToken> : ParsableStream<Lexer<TToken>.Token> where T
|
||||
{
|
||||
return new ParsingException(Report.FormatMessage(token, message));
|
||||
}
|
||||
|
||||
public void Halt(Lexer<TToken>.Token token, string message)
|
||||
{
|
||||
throw new ParsingException(Report.FormatMessage(token, message));
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
public static class Report
|
||||
{
|
||||
public static Exception Error(ISourceLocation token, string message)
|
||||
public static ParsingException Error(ISourceLocation token, string message)
|
||||
{
|
||||
return new ParsingException(FormatMessage(token, message));
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
namespace DevDisciples.Parsing;
|
||||
namespace DevDisciples.Parsing;
|
||||
|
||||
public static class Visitor
|
||||
{
|
||||
public delegate void Visit(object visitee, params object[] args);
|
||||
public delegate TOut Visit<TOut>(object visitee, params object[] args);
|
||||
public delegate TOut Visit<TIn, TOut>(TIn visitee, params object[] args);
|
||||
public delegate void Visit<TVisitee>(TVisitee visitee);
|
||||
|
||||
public delegate void Visit<TVisitee, TContext>(TVisitee visitee, TContext context);
|
||||
|
||||
public delegate TOut Visit<TVisitee, TContext, TOut>(TVisitee visitee, TContext context);
|
||||
}
|
@ -1,42 +1,40 @@
|
||||
namespace DevDisciples.Parsing;
|
||||
namespace DevDisciples.Parsing;
|
||||
|
||||
public class VisitorContainer
|
||||
public class VisitorContainer<TBase>
|
||||
{
|
||||
private Dictionary<Type, Visitor.Visit> Visitors { get; } = new();
|
||||
private Visitor.Visit Default { get; set; } = default!;
|
||||
private Dictionary<Type, Visitor.Visit<TBase>> Visitors { get; } = new();
|
||||
private Visitor.Visit<TBase> Default { get; set; } = default!;
|
||||
|
||||
public void Register<TVisitee>(Visitor.Visit visitor)
|
||||
public void Register<T>(Visitor.Visit<TBase> visitor)
|
||||
{
|
||||
Visitors[typeof(TVisitee)] = visitor;
|
||||
Visitors[typeof(T)] = visitor;
|
||||
}
|
||||
|
||||
public Visitor.Visit this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||
public Visitor.Visit<TBase> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||
}
|
||||
|
||||
public class VisitorContainer<T>
|
||||
public class VisitorContainer<TBase, TContext>
|
||||
{
|
||||
protected Dictionary<Type, Visitor.Visit<T>> Visitors { get; } = new();
|
||||
public Visitor.Visit<T> Default { get; } = default!;
|
||||
private Dictionary<Type, Visitor.Visit<TBase, TContext>> Visitors { get; } = new();
|
||||
private Visitor.Visit<TBase, TContext> Default { get; set; } = default!;
|
||||
|
||||
|
||||
public VisitorContainer<T> Register<TVisitee>(Visitor.Visit<T> visitor)
|
||||
public void Register<T>(Visitor.Visit<TBase, TContext> visitor)
|
||||
{
|
||||
Visitors[typeof(TVisitee)] = visitor;
|
||||
return this;
|
||||
Visitors[typeof(T)] = visitor;
|
||||
}
|
||||
|
||||
public Visitor.Visit<T> this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default;
|
||||
public Visitor.Visit<TBase, TContext> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||
}
|
||||
|
||||
public class VisitorContainer<TIn, TOut>
|
||||
public class VisitorContainer<TBase, TContext, TResult>
|
||||
{
|
||||
protected Dictionary<Type, Visitor.Visit<TIn, TOut>> Visitors { get; } = new();
|
||||
public Visitor.Visit<TIn, TOut> Default { get; } = default!;
|
||||
private Dictionary<Type, Visitor.Visit<TBase, TContext, TResult>> Visitors { get; } = new();
|
||||
private Visitor.Visit<TBase, TContext, TResult> Default { get; set; } = default!;
|
||||
|
||||
public void Register<TVisitee>(Visitor.Visit<TIn, TOut> visitor)
|
||||
public void Register<T>(Visitor.Visit<TBase, TContext, TResult> visitor)
|
||||
{
|
||||
Visitors[typeof(TVisitee)] = visitor;
|
||||
Visitors[typeof(T)] = visitor;
|
||||
}
|
||||
|
||||
public Visitor.Visit<TIn, TOut> this[Type type] => Visitors.ContainsKey(type) ? Visitors[type] : Default;
|
||||
public Visitor.Visit<TBase, TContext, TResult> this[Type type] => Visitors.GetValueOrDefault(type, Default);
|
||||
}
|
Loading…
Reference in New Issue
Block a user