Skip to content

Commit 1b72081

Browse files
committedJul 18, 2024·
added json-e lessons
1 parent 13c2b7c commit 1b72081

File tree

12 files changed

+223
-13
lines changed

12 files changed

+223
-13
lines changed
 

‎LearnJsonEverything.LessonEditor/MainWindow.xaml

+6-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,17 @@
1515
<Window.Resources>
1616
<hosts:SchemaHost x:Key="SchemaHost"/>
1717
<hosts:PathHost x:Key="PathHost"/>
18+
<hosts:JsonEHost x:Key="JsonEHost"/>
1819
</Window.Resources>
1920
<TabControl>
20-
<TabItem Header="Schema">
21+
<TabItem Header="JSON Schema">
2122
<controls:Editor FileName="schema.json" LessonHost="{StaticResource SchemaHost}"/>
2223
</TabItem>
23-
<TabItem Header="Path">
24+
<TabItem Header="JSON Path">
2425
<controls:Editor FileName="path.json" LessonHost="{StaticResource PathHost}"/>
2526
</TabItem>
27+
<TabItem Header="JSON-e">
28+
<controls:Editor FileName="json-e.json" LessonHost="{StaticResource JsonEHost}"/>
29+
</TabItem>
2630
</TabControl>
2731
</Window>

‎LearnJsonEverything.LessonEditor/ReferenceLoader.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Json.JsonE;
12
using Json.More;
23
using Json.Path;
34
using Json.Schema;
@@ -16,6 +17,7 @@ static ReferenceLoader()
1617
Load<JsonSchema>();
1718
Load<MinimumAttribute>();
1819
Load<JsonPath>();
20+
Load<JsonFunction>();
1921
}
2022

2123
private static void Load<T>(){}

‎LearnJsonEverything.Tests/ProvidedSolutionTests.cs

+18
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,22 @@ public void Path(LessonData lesson)
6767
Assert.That(result, Does.StartWith(Iconography.SuccessIcon));
6868
}
6969
}
70+
71+
public static IEnumerable<TestCaseData> JsonELessons => GetLessons("json-e.json");
72+
73+
[TestCaseSource(nameof(JsonELessons))]
74+
public void JsonE(LessonData lesson)
75+
{
76+
var results = new JsonEHost().Run(lesson);
77+
78+
foreach (var result in results)
79+
{
80+
Console.WriteLine(result);
81+
}
82+
83+
foreach (var result in results)
84+
{
85+
Assert.That(result, Does.StartWith(Iconography.SuccessIcon));
86+
}
87+
}
7088
}

‎LearnJsonEverything.Tests/ReferenceLoader.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Json.JsonE;
12
using Json.More;
23
using Json.Path;
34
using Json.Schema;
@@ -16,6 +17,7 @@ static ReferenceLoader()
1617
Load<JsonSchema>();
1718
Load<MinimumAttribute>();
1819
Load<JsonPath>();
20+
Load<JsonFunction>();
1921
}
2022

2123
private static void Load<T>(){}

‎LearnJsonEverything/LearnJsonEverything.csproj

+6-5
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@
1414
<ItemGroup>
1515
<PackageReference Include="Blazored.LocalStorage" Version="4.5.0" />
1616
<PackageReference Include="BlazorMonaco" Version="3.2.0" />
17-
<PackageReference Include="JsonPath.Net" Version="1.1.1" />
18-
<PackageReference Include="JsonSchema.Net" Version="7.0.4" />
17+
<PackageReference Include="JsonE.Net" Version="2.1.2" />
18+
<PackageReference Include="JsonPath.Net" Version="1.1.2" />
19+
<PackageReference Include="JsonSchema.Net" Version="7.1.2" />
1920
<PackageReference Include="JsonSchema.Net.Generation" Version="4.3.0.2" />
2021
<PackageReference Include="Markdig" Version="0.37.0" />
2122
<PackageReference Include="Markdig.SyntaxHighlighting" Version="1.1.7" />
22-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.5" />
23-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.5" PrivateAssets="all" />
24-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.9.2" />
23+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
24+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.7" PrivateAssets="all" />
25+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.10.0" />
2526
</ItemGroup>
2627

2728
<ItemGroup>

‎LearnJsonEverything/Pages/JsonE.razor

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@page "/json-e"
2+
@using LearnJsonEverything.Services.Hosts
3+
4+
<Teacher LessonSource="/data/lessons/json-e.json" Host="@_host"></Teacher>
5+
6+
@code {
7+
private readonly ILessonHost _host = new PathHost();
8+
}

‎LearnJsonEverything/Services/CompilationHelpers.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public static class CompilationHelpers
1515
private static readonly string[] EnsuredAssemblies =
1616
[
1717
"Json.More",
18+
"JsonE.Net",
1819
"JsonPath.Net",
1920
"JsonPointer.Net",
2021
"JsonSchema.Net",
@@ -76,7 +77,7 @@ public static (ILessonRunner<T>?, string[]) GetRunner<T>(LessonData lesson)
7677

7778
Console.WriteLine($"Compiling...\n\n{fullSource}");
7879

79-
var syntaxTree = CSharpSyntaxTree.ParseText(fullSource);
80+
var syntaxTree = CSharpSyntaxTree.ParseText(fullSource, new CSharpParseOptions(LanguageVersion.Latest));
8081
var assemblyPath = Path.ChangeExtension(Path.GetTempFileName(), "dll");
8182

8283
var compilation = CSharpCompilation.Create(Path.GetFileName(assemblyPath))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using System.Text.Json.Nodes;
2+
using Json.More;
3+
4+
namespace LearnJsonEverything.Services.Hosts;
5+
6+
public class JsonEHost : ILessonHost
7+
{
8+
public string[] Run(LessonData lesson)
9+
{
10+
var (runner, errors) = CompilationHelpers.GetRunner<JsonNode?>(lesson);
11+
12+
if (runner is null) return errors;
13+
14+
var results = new List<string>();
15+
16+
var correct = true;
17+
foreach (var test in lesson.Tests)
18+
{
19+
var expectedResult = test!["result"];
20+
JsonNode? result = null;
21+
// need to capture this first because some of the lessons add functions, which don't print so well.
22+
var printedTest = test.Print();
23+
try
24+
{
25+
result = runner.Run(test.AsObject());
26+
}
27+
catch
28+
{
29+
// ignore
30+
}
31+
32+
var localResult = expectedResult.IsEquivalentTo(result);
33+
correct &= localResult;
34+
results.Add($"{(localResult ? Iconography.SuccessIcon : Iconography.ErrorIcon)} {printedTest}");
35+
}
36+
37+
lesson.Achieved |= correct;
38+
39+
return [.. results];
40+
}
41+
}

‎LearnJsonEverything/Services/SerializationHelpers.cs

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.CodeAnalysis.CSharp;
44
using System.Text.Json.Nodes;
55
using System.Text.Json.Serialization;
6+
using Json.JsonE;
67
using Json.More;
78
using Json.Schema;
89

@@ -29,6 +30,7 @@ public static string ToLiteral(this string valueTextForCompiler)
2930

3031
[JsonSerializable(typeof(JsonSchema))]
3132
[JsonSerializable(typeof(EvaluationResults))]
33+
[JsonSerializable(typeof(JsonFunction))]
3234
[JsonSerializable(typeof(JsonNode))]
3335
[JsonSerializable(typeof(JsonObject))]
3436
[JsonSerializable(typeof(JsonArray))]

‎LearnJsonEverything/Shared/NavMenu.razor

+1-4
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
<div class="feature-description text-muted-light">Query JSON data - "XPath for JSON"</div>
1414
</div>
1515
</NavLink>
16-
<NavLink class="col mx-3 text-center btn-primary header-btn disabled" href="json-e">
17-
<div class="stamp">
18-
COMING SOON
19-
</div>
16+
<NavLink class="col mx-3 text-center btn-primary header-btn" href="json-e">
2017
<div>
2118
<div>JSON-e</div>
2219
<div class="feature-description text-muted-light">Templating and transformation of JSON data</div>

‎LearnJsonEverything/wwwroot/css/app.css

-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,6 @@ blockquote {
347347
padding: 9.5px;
348348
margin: 0 20px 15px;
349349
font-size: 13px;
350-
word-break: break-all;
351350
word-wrap: break-word;
352351
background-color: rgba(127, 127, 127, 0.2);
353352
border-radius: 6px;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
[
2+
{
3+
"id": "391f6512-f9f2-47be-94f8-315788b76638",
4+
"skip": false,
5+
"title": "Basic evaluation",
6+
"background": "JSON-e is a JSON-based templating language that allows you to perform actions and evaluate expressions in order to calculate or otherwise transform JSON data.\r\n\r\nAn evaluation typically consists of two parts: a template and a context, both of which are expressed in JSON. The context is optional, but to get the most of the template, you'll generally provide one.\r\n\r\nThe template and context _**must**_ both be objects, and the template can have at most single operation, a special property that starts with a dollar sign `$`.\r\n\r\nRead more about JSON-e on [their website](https://json-e.js.org/).\r\n\r\nTo evaluate a template, you'll use the `JsonE.Evaluate()` method, passing in the template and context as parameters.",
7+
"docs": "/json-e/basics",
8+
"api": null,
9+
"schemaDocs": null,
10+
"instructions": "Evaluate the template and the context. Return the result.",
11+
"contextCode": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n return /* USER CODE */;\r\n }\r\n}",
12+
"tests": [
13+
{
14+
"template": {
15+
"message": "hello ${key}",
16+
"k=${num}": true
17+
},
18+
"context": {
19+
"key": "world",
20+
"num": 1
21+
},
22+
"result": {
23+
"message": "hello world",
24+
"k=1": true
25+
}
26+
}
27+
],
28+
"solution": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}"
29+
},
30+
{
31+
"id": "135d89b9-c35d-4320-a92f-cb1c313a8438",
32+
"skip": false,
33+
"title": "Functions as Values",
34+
"background": "Internally, JSON-e uses an extended JSON model that supports functions as values. When an expression contains a function, such as the `max()` function in\r\n\r\n```json\r\n{\"$eval\": \"max(2, 4, 6)\"}\r\n```\r\n\r\nthe `max` symbol is fetched from the context, just like any other symbol. Because of the parameter list syntax following the `max` symbol, JSON-e expects a function that can process those parameters. The return value can be anything in the JSON-e model, so JSON values or even references to other functions.\r\n\r\nThe only catch is that a function _**must not**_ be returned as part of the final output. An error will result if this happens. JSON in; JSON out.\r\n\r\nJSON-e prescribes a number of built-in functions that must be supported. See [their documentation](https://json-e.js.org/built-ins.html) for the list of supported functions.",
35+
"docs": "/json-e/basics/#built-in-functions",
36+
"api": null,
37+
"schemaDocs": null,
38+
"instructions": "Complete the template to include a `max()` function that compares the `x` and `y` values in the context.",
39+
"contextCode": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var context = test[\"context\"];\r\n\r\n var template = new JsonObject\r\n {\r\n [\"$eval\"] = \"/* USER CODE */\"\r\n };\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}",
40+
"tests": [
41+
{
42+
"context": {
43+
"x": 5,
44+
"y": 1
45+
},
46+
"result": 5
47+
},
48+
{
49+
"context": {
50+
"x": 5,
51+
"y": 15
52+
},
53+
"result": 15
54+
},
55+
{
56+
"context": {
57+
"x": 5,
58+
"y": 21
59+
},
60+
"result": 21
61+
}
62+
],
63+
"solution": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var context = test[\"context\"];\r\n\r\n var template = new JsonObject\r\n {\r\n [\"$eval\"] = \"max(x, y)\"\r\n };\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}"
64+
},
65+
{
66+
"id": "6a83a410-4f60-4f10-90a7-94be921e4daa",
67+
"skip": false,
68+
"title": "Custom Functions",
69+
"background": "While JSON-e prescribes a fair number of built-in functions, it also requires that implementations support custom functions. _JsonE.Net_ handles this by providing the `JsonFunction` class, which operates seamlessly with the `JsonNode` family of types.\r\n\r\nThe `JsonFunction` class exposes a simple `.Create()` method which takes a lambda expression to represent the function. The parameters for the lambda are:\r\n\r\n- `parameters` - which is an array of `JsonNode`s (`JsonNode?[]`, not `JsonArray`). These will be filled with the values from the parameter list. (If they're variables in the expression, they'll already be resolved.)\r\n- `context` - which is just the context object in case you need to access more than what's given in the parameters.\r\n\r\nWhen accessing the parameters, you will need to perform any type checking, such as ensuring that you've been given the right values types.",
70+
"docs": "/json-e/basics/#custom-functions",
71+
"api": null,
72+
"schemaDocs": null,
73+
"instructions": "Add a `mod` function to the context.",
74+
"contextCode": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\nusing Json.More;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n /* USER CODE */\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}",
75+
"tests": [
76+
{
77+
"template": {
78+
"$eval": "mod(x,y)"
79+
},
80+
"context": {
81+
"x": 10,
82+
"y": 4
83+
},
84+
"result": 2
85+
}
86+
],
87+
"solution": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\nusing Json.More;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n context[\"mod\"] = JsonFunction.Create((parameters, context) =>\r\n parameters[0]!.AsValue().GetNumber() % parameters[1]!.AsValue().GetNumber()\r\n );\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}"
88+
},
89+
{
90+
"id": "84a80f6c-9b0f-4f9b-97e2-7da2eac1160b",
91+
"skip": false,
92+
"title": "Error Handling",
93+
"background": "Various errors can occur while processing JSON-e templates.\r\n\r\n| Error Type | Thrown when... |\r\n|:-|:-|\r\n|Built-In | something goes wrong function evaluation |\r\n|Interpreter | a template can't be evaluated |\r\n|Syntax | an expression contains an invalid syntax |\r\n|Template| a template is invalid |\r\n|Type | ... in some cases. None of the test cases result in this error, but it is defined so... ¯\\\\\\_(ツ)_/¯ |\r\n\r\n> These error types, and even the error messages, are specified by JSON-e. _JsonE.Net_ has been urged to keep the error types and messaging as consistent with the other libraries as possible. It's not perfect, but it's pretty close.\r\n{: .prompt-info}",
94+
"docs": "/json-e/basics/#errors",
95+
"api": null,
96+
"schemaDocs": null,
97+
"instructions": "Wrap the evaluation in a try/catch. Return the exception type name.",
98+
"contextCode": "using System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n /* USER CODE */\r\n\r\n return JsonE.Evaluate(template, context);\r\n }\r\n}",
99+
"tests": [
100+
{
101+
"template": {
102+
"$reverse": true
103+
},
104+
"context": {},
105+
"result": "TemplateException"
106+
},
107+
{
108+
"template": {
109+
"$eval": "key.length"
110+
},
111+
"context": {
112+
"key": "12345"
113+
},
114+
"result": "InterpreterException"
115+
},
116+
{
117+
"template": {
118+
"$eval": "ceil(true)"
119+
},
120+
"context": {},
121+
"result": "BuiltInException"
122+
},
123+
{
124+
"template": {
125+
"$eval": "key.{"
126+
},
127+
"context": {
128+
"key": {}
129+
},
130+
"result": "SyntaxException"
131+
}
132+
],
133+
"solution": "using System;\r\nusing System.Text.Json;\r\nusing System.Text.Json.Nodes;\r\nusing Json.JsonE;\r\n\r\nnamespace LearnJsonEverything;\r\n\r\npublic class Lesson : ILessonRunner<JsonNode?>\r\n{\r\n public JsonNode? Run(JsonObject test)\r\n {\r\n var template = test[\"template\"];\r\n var context = test[\"context\"];\r\n\r\n try\r\n {\r\n return JsonE.Evaluate(template, context);\r\n }\r\n catch (JsonEException e)\r\n {\r\n Console.WriteLine(e.Message); // check the browser console!\r\n return e.GetType().Name;\r\n }\r\n }\r\n}"
134+
}
135+
]

0 commit comments

Comments
 (0)
Please sign in to comment.