Orbyss.Blazor.JsonForms
1.0.3
dotnet add package Orbyss.Blazor.JsonForms --version 1.0.3
NuGet\Install-Package Orbyss.Blazor.JsonForms -Version 1.0.3
<PackageReference Include="Orbyss.Blazor.JsonForms" Version="1.0.3" />
<PackageVersion Include="Orbyss.Blazor.JsonForms" Version="1.0.3" />
<PackageReference Include="Orbyss.Blazor.JsonForms" />
paket add Orbyss.Blazor.JsonForms --version 1.0.3
#r "nuget: Orbyss.Blazor.JsonForms, 1.0.3"
#:package Orbyss.Blazor.JsonForms@1.0.3
#addin nuget:?package=Orbyss.Blazor.JsonForms&version=1.0.3
#tool nuget:?package=Orbyss.Blazor.JsonForms&version=1.0.3
π¦ Orbyss.Blazor.JsonForms
A fully .NET-native implementation of the JsonForms.io standard for schema-driven forms in Blazor.
No Angular, no web components β just C#, JSON Schema, and flexible Blazor architecture.
π― What is this?
This is the UI-agnostic core framework for rendering dynamic forms from JSON Schema in .NET. It handles:
- Form generation
- Schema interpretation
- Localization via translation schema
- Layout, validation, and data management
You plug in the UI layer, also called the 'FormComponentInstanceProvider'. This library is the form engine β and you must bring the renderer.
With an implemented UI layer, you can render the JsonForms in your code as follows:
<JsonForm InitOptions=@options/>
@code{
JsonFormContextInitOptions options = new(
jsonSchema,
uiSchema,
translationSchema
);
}
βYou can specify JsonFormContext as parameter, or as a Transient Service (DI)
βYou can specify ComponentInstanceProvider as parameter, or as a DI service
βYou can provide the following cascading values to your JsonForm: "Language, Disabled, ReadOnly".
π Available UI Integrations
Use one of our ready-to-go UI packages:
Or you can build your own; for example when you have your own Blazor component system, or you are using other external frameworks such as Radzen, Telerik, or Fluent UI
π How to: Implement Your Own UI Layer
β Start by implementing the following interface
public interface IFormComponentInstanceProvider
{
InputFormComponentInstanceBase GetInputField(IJsonFormContext context, FormControlContext control);
IFormComponentInstance GetGridRow(IFormElementContext? row);
IFormComponentInstance GetGridColumn(IFormElementContext? column);
IFormComponentInstance GetGrid(IJsonFormContext? form, FormPageContext? page);
ButtonFormComponentInstanceBase GetButton(FormButtonType type, IJsonFormContext? form);
NavigationFormComponentInstanceBase GetNavigation(IJsonFormContext formContext);
ListFormComponentInstanceBase GetList(FormListContext? list = null);
ListItemFormComponentInstance GetListItem(IFormElementContext? listItem = null);
}
Example of a GetInputField implementation:
public virtual InputFormComponentInstanceBase GetInputField(IJsonFormContext context, FormControlContext control)
{
var type = control.Interpretation.ControlType;
return type switch
{
ControlType.Boolean => GetBooleanField(control),
ControlType.String => GetTextField(control),
ControlType.Enum => GetDropDownField(control),
ControlType.EnumList => GetMultiDropDownField(control),
ControlType.DateTime => GetDateTimeField(control),
ControlType.DateOnly => GetDateOnlyField(control),
ControlType.DateOnlyUtcTicks => GetDateUtcTicksField(control),
ControlType.DateTimeUtcTicks => GetDateTimeUtcTicksField(control),
ControlType.Integer => GetIntegerField(control),
ControlType.Number => GetNumberField(control),
_=> throw new NotSupportedException($"Cannot create an input field for type '{type}'")
};
}
π§± Step-by-Step Guide to Building a Custom Component
β 1. Create your Razor input component
<SfTextBox Type=InputType.Number
CssClass="@FullClass"
Enabled=@(!Disabled)
Readonly=ReadOnly
Value="@Value"
Placeholder="@Label"
FloatLabelType="FloatLabelType.Always"
ID="@id"
ValueChanged="@OnValueChanged"
ShowClearButton=@Clearable
Width="@Width" />
@if (HasError)
{
<div class="e-error validation-message">@ErrorHelperText</div>
}
else if (!string.IsNullOrWhiteSpace(HelperText))
{
<div class="validation-message "><i>@HelperText</i></div>
}
Add these standard parameters:
[Parameter] public string? Label { get; internal set; }
[Parameter] public bool Disabled { get; internal set; }
[Parameter] public bool ReadOnly { get; internal set; }
[Parameter] public string? ErrorHelperText { get; internal set; }
[Parameter] public string? HelperText { get; set; }
// Required: runtime error thrown when not specified
[Parameter] public string Value { get; set; }
// Required: runtime error thrown when not specified
[Parameter] public EventCallback<string> OnValueChanged { get; set; }
β οΈ If you forget to invoke OnValueChanged, your input wonβt update the form state!
β οΈ The control types are fixed. You must return the right Value/OnValueChanged<T> pair for each field type. See the table below.
public static class ControlTypeLookup
{
public static readonly Type Enum = typeof(string);
public static readonly Type EnumList = typeof(IEnumerable<string>);
public static readonly Type DateTime = typeof(DateTime?);
public static readonly Type DateTimeUtcTicks = typeof(DateTimeUtcTicks?);
public static readonly Type DateOnly = typeof(DateOnly?);
public static readonly Type DateOnlyUtcTicks = typeof(DateUtcTicks?);
public static readonly Type Number = typeof(double?);
public static readonly Type Integer = typeof(int?);
public static readonly Type String = typeof(string);
public static readonly Type Boolean = typeof(bool);
public static Type GetForControlType(ControlType controlType) => fieldsPerControlType[controlType];
}
Example: If your schema field is "type": "integer", your component must have:
[Parameter] public int? Value { get; set; }
[Parameter] public EventCallback<int?> OnValueChanged { get; set; }
β 2. Create a component instance class
This class represents the contract of your component, and is used internally to map component parameters from the IFormComponentInstanceProvider implementation to your razor components. When the standard parameters are sufficient for your components, you can simply make use of our built-in instances:
public virtual ListItemFormComponentInstance GetListItem(IFormElementContext? listItem = null)
{
return new ListItemFormComponentInstance<SyncfusionFormListItem>();
}
If the built-in component instances do not provide all the parameters you need for your own components, you will have to derive from one of the (base)classes and add your own parameters.
Custom razor component:
<SfSwitch HtmlAttributes=@htmlAttributes
Checked="@Value"
CssClass="@Class"
TChecked="bool"
CheckedChanged="OnValueChanged"
Disabled=@(Disabled || ReadOnly)
OffLabel="@OffLabel"
OnLabel="@OnLabel" />
[Parameter]
public string? OnLabel { get; set; }
[Parameter]
public string? OffLabel { get; set; }
Custom component instance:
public class MyCustomSwitchInstance : InputFormComponentInstance<MyCustomSwitch>
{
public MyCustomSwitchInstance() : base(t => (bool?)t)
{
}
public string? OnLabel { get; set; }
public string? OffLabel { get; set; }
protected override IDictionary<string, object?> GetFormInputParameters()
{
return new Dictionary<string, object?>
{
[nameof(MyCustomSwitch.OffLabel)] = OffLabel,
[nameof(MyCustomSwitch.OnLabel)] = OnLabel
};
}
}
β 3. Return your instance
protected virtual InputFormComponentInstanceBase GetBooleanField(FormControlContext control)
{
var booleanControlType = control.Interpretation.GetOption("custom-bool-type");
if($"{booleanControlType}" == "switch")
{
return new MyCustomSwitchInstance();
}
else
{
return new SyncfusionCheckboxInstance();
}
}
π‘ You can make use of (custom) options that you can configure in your UI Schema. See jsonforms.io docs
π How The Framework Works
The framework generates forms using three different schemas:
Schema Type | Description |
---|---|
JSON Schema | Defines the data structure (types, enums, etc.) |
UI Schema | Controls layout, grouping, (custom) options, rules |
Translation Schema | Provides localized labels, errors, and enums |
Example:
// JSON Schema
{
"type": "object",
"properties": {
"firstName": {
"type": "string",
"minLength": 21
},
"surname": {
"type": "string"
}
},
"required":[
"firstName"
]
}
// UI Schema
{
"type": "VerticalLayout",
"elements": [
{
"type": "Control",
"scope": "#/properties/firstName"
},
{
"type": "Control",
"scope": "#/properties/surname",
"options": {
"hidden": true
},
"rule":{
"effect": "Show",
"condition":{
"scope": "#/properties/firstName",
"schema":{
"minLength": 2
}
}
}
}
],
"options": {
"customOption": "custom-option-value"
}
}
// Translation Schema
{
"resources": {
"en": {
"translation": {
"firstName": {
"label": "First Name",
"error":{
"minLength": "Must have minimum of 21 characters"
}
},
"surname": {
"label": "Surname"
},
"customLabel": "Special"
}
},
"nl": {
"translation": {
"firstName": {
"label": "Voornaam",
"error":{
"minLength": "Moet minimaal 21 karakters bevatten"
}
},
"surname": {
"label": "Achternaam"
},
"customLabel": "Speciaal"
}
}
}
}
π¦ Installation
dotnet add package Orbyss.Components.JsonForms
Then reference a UI implementation package or build your own.
π License
MIT License Β© Orbyss
π Links
- π Website: https://orbyss.io
- π¦ NuGet: Orbyss.Blazor.JsonForms
- π§βπ» GitHub: https://github.com/Orbyss-io
- π License: MIT
- JsonForms.io
- Syncfusion UI integration
- MudBlazor UI integration
π€ Contributing
This project is open source and contributions are welcome!
Whether it's bug fixes, improvements, documentation, or ideas β we encourage developers to get involved.
Just fork the repo, create a branch, and open a pull request.
We follow standard .NET open-source conventions:
- Write clean, readable code
- Keep PRs focused and descriptive
- Open issues for larger features or discussions
No formal contribution guidelines β just be constructive and respectful.
βοΈ If you find this useful, give us a star and help spread the word!
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- JLio.Client (>= 1.2.1)
- Newtonsoft.Json.Schema (>= 4.0.1)
- Orbyss.Components.Json.Models (>= 1.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Orbyss.Blazor.JsonForms:
Package | Downloads |
---|---|
Orbyss.Blazor.Syncfusion.JsonForms
Syncfusion UI-based renderer for the Orbyss.Blazor.JsonForms form generation engine β a fully .NET-native schema-driven forms framework built on JsonForms.io concepts. |
|
Orbyss.Blazor.MudBlazor.JsonForms
MudBlazor UI-based renderer for the Orbyss.Blazor.JsonForms form generation engine β a fully .NET-native schema-driven forms framework built on JsonForms.io concepts. |
GitHub repositories
This package is not used by any popular GitHub repositories.
*Release: 8 August, 2025*
- Found minor bug in TranslationContext; when language was null, the property name for labels was returned right away.
Now, we will first check if there are any translations, and only when there is not other option left will we return the property name