I am wondering if there is a simple and best way to convert a heavily nested json into a editable tables on a WPF form where the user can edit the table. After editing is done i want to convert the edited table back to json again. I am new to C# . I would really appreciate any suggestion on how to achieve this.
PS: The json is dynamic (new keys can be added or deleted and the structure might change).
This is pretty straightforward:
System.Text.Json
as the modelJsonArray
, JsonObject
and JsonValue
all are derived from JsonNode
)TreeView
and the HierarchicalDataTemplate
, the view model must also know the key matching the JsonNode
.This is a first draft for a read-only version.
The code is available here.
public enum JsonKeyType
{
Root,
Index,
Parameter
}
public abstract class JsonKey
{
public abstract JsonKeyType KeyType { get; }
}
public class IndexKey : JsonKey
{
public IndexKey(int index)
{
Index = index;
}
public int Index { get; }
public override JsonKeyType KeyType => JsonKeyType.Index;
public override string ToString() => $"@{Index}";
}
public class PropertyKey : JsonKey
{
public PropertyKey(string key)
{
Key = key;
}
public string Key { get; }
public override JsonKeyType KeyType => JsonKeyType.Parameter;
public override string ToString() => Key;
}
public class RootKey : JsonKey
{
public static RootKey Instance { get; } = new();
private RootKey()
{
}
public override JsonKeyType KeyType => JsonKeyType.Root;
public override string ToString() => "Root";
}
public abstract class JsonViewModel
{
public static JsonViewModel? From(JsonKey key, JsonNode? jsonNode)
{
return jsonNode switch
{
JsonArray jsonArray => new JsonArrayViewModel(key, jsonArray),
JsonObject jsonObject => new JsonObjectViewModel(key, jsonObject),
JsonValue jsonValue => new JsonValueViewModel(key, jsonValue),
null => new JsonValueViewModel(key, null),
_ => throw new UnreachableException()
};
}
protected JsonViewModel(JsonKey key)
{
Key = key;
}
public JsonKey Key { get; }
}
public class JsonArrayViewModel : JsonViewModel
{
private readonly JsonArray _jsonArray;
public JsonArrayViewModel(JsonKey key, JsonArray jsonArray) : base(key)
{
_jsonArray = jsonArray;
}
public IEnumerable<JsonViewModel?> Values => _jsonArray.Select((node, index) => From(new IndexKey(index), node));
}
public class JsonObjectViewModel : JsonViewModel
{
private readonly JsonObject _jsonObject;
public JsonObjectViewModel(JsonKey key, JsonObject jsonObject) : base(key)
{
_jsonObject = jsonObject;
}
public IEnumerable<JsonViewModel?> Values => _jsonObject.Select(kvp => From(new PropertyKey(kvp.Key), kvp.Value));
}
public class JsonValueViewModel : JsonViewModel
{
public JsonValueViewModel(JsonKey key, JsonValue? jsonValue) : base(key)
{
if (jsonValue is null)
{
Value = "null";
ValueKind = JsonValueKind.Null;
}
else
{
var jsonElement = jsonValue.GetValue<JsonElement>();
Value = jsonElement.GetRawText();
ValueKind = jsonElement.ValueKind;
}
}
public string Value { get; }
public JsonValueKind ValueKind { get; }
}
<TreeView ItemsSource="{Binding}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type local:JsonArrayViewModel}" ItemsSource="{Binding Values}">
<TextBlock Text="{Binding Key}" Foreground="Gray" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:JsonObjectViewModel}" ItemsSource="{Binding Values}">
<TextBlock Text="{Binding Key}" Foreground="Gray" />
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type local:JsonValueViewModel}">
<StackPanel Orientation="Horizontal"
ToolTip="{Binding ValueKind}">
<TextBlock Text="{Binding Key, StringFormat='{}{0}: '}" Foreground="Gray">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding Key.KeyType}" Value="{x:Static local:JsonKeyType.Index}">
<Setter Property="FontStyle" Value="Italic" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
<TextBlock Text="{Binding Value}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>