diff --git a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs index 3ef2a72..b9dde65 100644 --- a/LearnJsonEverything.Tests/ProvidedSolutionTests.cs +++ b/LearnJsonEverything.Tests/ProvidedSolutionTests.cs @@ -7,7 +7,7 @@ namespace LearnJsonEverything.Tests; public class ProvidedSolutionTests { - private static JsonSerializerOptions SerializerOptions = + private static readonly JsonSerializerOptions SerializerOptions = new() { PropertyNameCaseInsensitive = true @@ -43,7 +43,36 @@ public static IEnumerable SchemaLessons [TestCaseSource(nameof(SchemaLessons))] public void Schema(LessonData lesson) { - var results = SchemaRunner.Run(lesson); + var results = new SchemaHost().Run(lesson); + + foreach (var result in results) + { + Console.WriteLine(result); + } + + foreach (var result in results) + { + Assert.That(result, Does.StartWith(Iconography.SuccessIcon)); + } + } + + public static IEnumerable PathLessons + { + get + { + var lessonPlan = LoadLessonPlan("path.yaml"); + return lessonPlan.Select(x => + { + x.UserCode = x.Solution; + return new TestCaseData(x) { TestName = x.Title }; + }); + } + } + + [TestCaseSource(nameof(PathLessons))] + public void Path(LessonData lesson) + { + var results = new PathHost().Run(lesson); foreach (var result in results) { diff --git a/LearnJsonEverything.Tests/ReferenceLoader.cs b/LearnJsonEverything.Tests/ReferenceLoader.cs index 6da525d..e1c59b0 100644 --- a/LearnJsonEverything.Tests/ReferenceLoader.cs +++ b/LearnJsonEverything.Tests/ReferenceLoader.cs @@ -1,5 +1,5 @@ -using System.Reflection; -using System.Text.Json.Nodes; +using Json.More; +using Json.Path; using Json.Schema; using Json.Schema.Generation; using Microsoft.CodeAnalysis; @@ -9,20 +9,19 @@ namespace LearnJsonEverything.Tests; public static class ReferenceLoader { - private class NullRunner : ILessonRunner - { - public int Run(JsonObject context) => 0; - } - static ReferenceLoader() { // force some assemblies to load - SchemaRegistry.Global.Fetch = null!; + Load>(); + Load>(); + Load(); + Load(); + Load(); _ = YamlSerializer.Parse(string.Empty); - _ = new NullRunner(); - _ = typeof(NullRunner).GetCustomAttributes(); } + private static void Load(){} + public static MetadataReference[] Load() { var refs = AppDomain.CurrentDomain.GetAssemblies(); diff --git a/LearnJsonEverything/LearnJsonEverything.csproj b/LearnJsonEverything/LearnJsonEverything.csproj index 6f277ac..4acf853 100644 --- a/LearnJsonEverything/LearnJsonEverything.csproj +++ b/LearnJsonEverything/LearnJsonEverything.csproj @@ -14,6 +14,7 @@ + diff --git a/LearnJsonEverything/Pages/Path.razor b/LearnJsonEverything/Pages/Path.razor new file mode 100644 index 0000000..acedb06 --- /dev/null +++ b/LearnJsonEverything/Pages/Path.razor @@ -0,0 +1,8 @@ +@page "/json-path" +@using LearnJsonEverything.Services.Runners + + + +@code { + private readonly ILessonHost _host = new PathHost(); +} \ No newline at end of file diff --git a/LearnJsonEverything/Pages/Schema.razor b/LearnJsonEverything/Pages/Schema.razor index f346683..a4349a9 100644 --- a/LearnJsonEverything/Pages/Schema.razor +++ b/LearnJsonEverything/Pages/Schema.razor @@ -1,7 +1,8 @@ @page "/json-schema" +@using LearnJsonEverything.Services.Runners - + @code { - + private readonly ILessonHost _host = new SchemaHost(); } \ No newline at end of file diff --git a/LearnJsonEverything/Services/CompilationHelpers.cs b/LearnJsonEverything/Services/CompilationHelpers.cs index 1cb2365..d43f18a 100644 --- a/LearnJsonEverything/Services/CompilationHelpers.cs +++ b/LearnJsonEverything/Services/CompilationHelpers.cs @@ -15,6 +15,7 @@ public static class CompilationHelpers private static readonly string[] EnsuredAssemblies = [ "Json.More", + "JsonPath.Net", "JsonPointer.Net", "JsonSchema.Net", "JsonSchema.Net.Generation", diff --git a/LearnJsonEverything/Services/Runners/ILessonHost.cs b/LearnJsonEverything/Services/Runners/ILessonHost.cs new file mode 100644 index 0000000..5f3d052 --- /dev/null +++ b/LearnJsonEverything/Services/Runners/ILessonHost.cs @@ -0,0 +1,6 @@ +namespace LearnJsonEverything.Services.Runners; + +public interface ILessonHost +{ + string[] Run(LessonData lesson); +} \ No newline at end of file diff --git a/LearnJsonEverything/Services/Runners/PathHost.cs b/LearnJsonEverything/Services/Runners/PathHost.cs new file mode 100644 index 0000000..662669d --- /dev/null +++ b/LearnJsonEverything/Services/Runners/PathHost.cs @@ -0,0 +1,30 @@ +using Json.More; +using Json.Path; + +namespace LearnJsonEverything.Services.Runners; + +public class PathHost : ILessonHost +{ + public string[] Run(LessonData lesson) + { + var (runner, errors) = CompilationHelpers.GetRunner(lesson); + + if (runner is null) return errors; + + var results = new List(); + + var correct = true; + foreach (var test in lesson.Tests) + { + var expectedResult = test!["result"]; + var result = runner.Run(test.AsObject()); + var localResult = expectedResult.IsEquivalentTo(result.Matches?.Select(x => x.Value).ToJsonArray()); + correct &= localResult; + results.Add($"{(localResult ? Iconography.SuccessIcon : Iconography.ErrorIcon)} {test["data"]!.Print()}"); + } + + lesson.Achieved |= correct; + + return [.. results]; + } +} \ No newline at end of file diff --git a/LearnJsonEverything/Services/Runners/SchemaRunner.cs b/LearnJsonEverything/Services/Runners/SchemaHost.cs similarity index 88% rename from LearnJsonEverything/Services/Runners/SchemaRunner.cs rename to LearnJsonEverything/Services/Runners/SchemaHost.cs index dc03346..e1c0e69 100644 --- a/LearnJsonEverything/Services/Runners/SchemaRunner.cs +++ b/LearnJsonEverything/Services/Runners/SchemaHost.cs @@ -2,9 +2,9 @@ namespace LearnJsonEverything.Services.Runners; -public static class SchemaRunner +public class SchemaHost : ILessonHost { - public static string[] Run(LessonData lesson) + public string[] Run(LessonData lesson) { var (runner, errors) = CompilationHelpers.GetRunner(lesson); @@ -25,4 +25,4 @@ public static string[] Run(LessonData lesson) return [.. results]; } -} +} \ No newline at end of file diff --git a/LearnJsonEverything/Shared/NavMenu.razor b/LearnJsonEverything/Shared/NavMenu.razor index fb5a107..951dcf4 100644 --- a/LearnJsonEverything/Shared/NavMenu.razor +++ b/LearnJsonEverything/Shared/NavMenu.razor @@ -7,10 +7,7 @@
Constraints-based validation of JSON data
- -
- COMING SOON -
+
JSON Path
Query JSON data - "XPath for JSON"
diff --git a/LearnJsonEverything/Shared/Teacher.razor b/LearnJsonEverything/Shared/Teacher.razor index 23b8257..e2f7f47 100644 --- a/LearnJsonEverything/Shared/Teacher.razor +++ b/LearnJsonEverything/Shared/Teacher.razor @@ -71,6 +71,8 @@ [Parameter] public string LessonSource { get; set; } + [Parameter] + public ILessonHost Host { get; set; } private string Instructions { get; set; } @@ -88,7 +90,7 @@ await _outputEditor.SetLanguageAsync("json", JsRuntime); _currentLesson!.UserCode = userCode; - var results = SchemaRunner.Run(_currentLesson); + var results = Host.Run(_currentLesson); await _outputEditor.SetValue(string.Join(Environment.NewLine, results!)); UpdateNavigation(); diff --git a/LearnJsonEverything/wwwroot/data/lessons/path.yaml b/LearnJsonEverything/wwwroot/data/lessons/path.yaml new file mode 100644 index 0000000..ad55a69 --- /dev/null +++ b/LearnJsonEverything/wwwroot/data/lessons/path.yaml @@ -0,0 +1,48 @@ +--- +- id: 26b6ebca-58e6-4814-86ea-494ed844c9a8 + background: | + JSON Path is a syntax for querying JSON data. + + _JsonPath.Net_ provides an implementation that conforms to the official IETF + specification, [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535.html). Like the other + guides on this site, this guide will teach you how to use the library _JsonPath.Net_. + + However, because there are so few implementations of the RFC, and little to no + documentation of it, this guide will also teach you the features of JSON Path itself, + as described by the RFC. + + We'll start with the library since there is less to cover, then we'll move on to + what you can do with it. + + Unlike JSON Schema, JSON Logic, or other technologies that are actually represented in + JSON, JSON Path is its own syntax, so it must usually be parsed. The primary way to + parse a path is using the static `JsonPath.Parse()` method. + docs: 'path/basics' + title: Parsing + instructions: | + Parse the given JSON Path text into a `path` variable. + inputTemplate: '' + contextCode: |- + using System.Text.Json; + using System.Text.Json.Nodes; + using Json.Path; + + namespace LearnJsonEverything; + + public class Lesson : ILessonRunner + { + public PathResult Run(JsonObject test) + { + var data = test["data"]; + var pathText = "$.foo.bar"; + + /* USER CODE */ + + return path.Evaluate(data); + } + } + solution: |- + var path = JsonPath.Parse(pathText); + tests: + - data: { "foo": { "bar": "a string" } } + result: ['a string']