Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for System.Text.Json.JsonElement #527

Closed
r-Larch opened this issue Dec 22, 2023 · 1 comment
Closed

Add Support for System.Text.Json.JsonElement #527

r-Larch opened this issue Dec 22, 2023 · 1 comment

Comments

@r-Larch
Copy link
Contributor

r-Larch commented Dec 22, 2023

Current behavior

Wenn using System.Text.Json deserializing to Dictionary<string, object> does not work, because it produces
Dictionary<string, JsonElement> and JsonElement can not be added directly to ScriptObject because JsonElement
has no public properties to be exposed in the scriban runtine.

See the following Example to understand how scriban handles JsonElement:

var model = System.Text.Json.JsonSerializer.Deserialize<JsonElement>("""{ model: { "foo": "bar" } }""");
var template = Template.Parse("{{ model | object.keys }}");
var result = template.Render(model);
// actual result -> ["value_kind"]
// expected result -> ["foo"]

Expected behavior

It should be possible to add JsonElement to a ScriptObject or ScriptArray while exposing the actual JSON fields to the runtime.

Motivation for Change

I think providing first class support for .NET build-in JSON types makes scriban a even more powerful engine.

How I solved it

To solve the problem I wrote a extension method for ScriptObject where I loop through the JsonElement and add all elements to a ScriptObject or ScriptArray.

Working Example:

var model = System.Text.JsonSerializer.Deserialize<JsonElement>("""{ model: { "foo": "bar" } }""");
var template = Template.Parse("{{ model | object.keys }}");
var scriptObject = new ScriptObject();
scriptObject.Import(model);
var result = template.Render(scriptObject);
// result -> ["foo"]

public static class Extensions {
  public static void Import(this IScriptObject script, JsonElement json)
  {
      if (json.ValueKind is not JsonValueKind.Object) throw new ArgumentOutOfRangeException($"Unsupported object type `{json.ValueKind}`. Expecting Json Object.");
  
      AddObject(json, script);
  
      object? Value(JsonElement model)
      {
          return model.ValueKind switch {
              JsonValueKind.Array => AddArray(model, new ScriptArray()),
              JsonValueKind.Object => AddObject(model, new ScriptObject()),
              JsonValueKind.False => false,
              JsonValueKind.True => true,
              JsonValueKind.Null => null,
              JsonValueKind.Number => model.GetDecimal(),
              JsonValueKind.String => model.GetString(),
              JsonValueKind.Undefined => null,
              _ => throw new ArgumentOutOfRangeException()
          };
      }
  
      IScriptObject AddObject(JsonElement model, IScriptObject obj)
      {
          foreach (var property in model.EnumerateObject()) {
              obj.SetValue(property.Name, Value(property.Value), false);
          }
  
          return obj;
      }
  
      ScriptArray AddArray(JsonElement model, ScriptArray array)
      {
          foreach (var value in model.EnumerateArray()) {
              array.Add(Value(value));
          }
  
          return array;
      }
  }
}

API Proposal

class ScriptObjectExtensions {
  public static void Import(this IScriptObject script, JsonElement json, MemberFilterDelegate filter = null, MemberRenamerDelegate renamer = null) {
    if (json.ValueKind is not JsonValueKind.Object) throw new ArgumentOutOfRangeException($"Unsupported object type `{json.ValueKind}`. Expecting Json Object.");
    ...
  }
  public static void Import(this IScriptObject script, object obj, MemberFilterDelegate filter = null, MemberRenamerDelegate renamer = null) {
    if (obj is JsonElement json) {
      script.Import(json, filter, renamer);
    }
    ...
  }
}
class ScriptObject {
  public void Add(string key, JsonElement value);
}
class ScriptArray {
  public void Add(JsonElement value);
}

Pull-Request?

I would provide a pull request.

@xoofx
Copy link
Member

xoofx commented Jan 9, 2024

I think it's ok to add a support for it only for net8.0+

This could be added also as a function to e.g object.from_json to allow to deserialize from a json string and potentially also a object.to_json to serialize to json properly.

Feel free to start a PR to see how it goes.

r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 11, 2024
…ban#527

- Introduced JsonElementExtensions.cs to convert JsonElements into Scriban objects
- Updated ScriptArray.cs and ScriptObject.cs to handle JsonElements
- Enhanced ScriptObjectExtensions.cs with methods for importing JsonElements into script objects
- Extended Template.cs with a Render method that accepts a JsonElement as input
- Added new test cases to validate JSON support
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…ban#527

- Introduced JsonElementExtensions.cs to convert JsonElements into Scriban objects
- Updated ScriptArray.cs and ScriptObject.cs to handle JsonElements
- Enhanced ScriptObjectExtensions.cs with methods for importing JsonElements into script objects
- Extended Template.cs with a Render method that accepts a JsonElement as input
- Added new test cases to validate JSON support
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…criban#527

- Implemented FromJson function to convert JSON to Scriban value
- Implemented ToJson function to convert Scriban value to JSON
- Added new test cases for JSON parsing and conversion in TestObjectFunctions.cs
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…ban#527

- Introduced JsonElementExtensions.cs to convert JsonElements into Scriban objects
- Updated ScriptArray.cs and ScriptObject.cs to handle JsonElements
- Enhanced ScriptObjectExtensions.cs with methods for importing JsonElements into script objects
- Extended Template.cs with a Render method that accepts a JsonElement as input
- Added new test cases to validate JSON support
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…criban#527

- Implemented FromJson function to convert JSON to Scriban value
- Implemented ToJson function to convert Scriban value to JSON
- Added new test cases for JSON parsing and conversion in TestObjectFunctions.cs
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…ban#527

- Introduced JsonElementExtensions.cs to convert JsonElements into Scriban objects
- Updated ScriptArray.cs and ScriptObject.cs to handle JsonElements
- Enhanced ScriptObjectExtensions.cs with methods for importing JsonElements into script objects
- Extended Template.cs with a Render method that accepts a JsonElement as input
- Added new test cases to validate JSON support
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…criban#527

- Implemented FromJson function to convert JSON to Scriban value
- Implemented ToJson function to convert Scriban value to JSON
- Added new test cases for JSON parsing and conversion in TestObjectFunctions.cs
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…ban#527

- Introduced JsonElementExtensions.cs to convert JsonElements into Scriban objects
- Updated ScriptArray.cs and ScriptObject.cs to handle JsonElements
- Enhanced ScriptObjectExtensions.cs with methods for importing JsonElements into script objects
- Extended Template.cs with a Render method that accepts a JsonElement as input
- Added new test cases to validate JSON support
r-Larch added a commit to r-Larch/scriban that referenced this issue Feb 12, 2024
…criban#527

- Implemented FromJson function to convert JSON to Scriban value
- Implemented ToJson function to convert Scriban value to JSON
- Added new test cases for JSON parsing and conversion in TestObjectFunctions.cs
@xoofx xoofx closed this as completed in f2f3cf4 Feb 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants