-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
345 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
179 changes: 179 additions & 0 deletions
179
DataToolChain.Ui/JsonToMsSqlQuery/JsonFlattenExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text.RegularExpressions; | ||
using Newtonsoft.Json.Linq; | ||
|
||
namespace DataToolChain | ||
{ | ||
|
||
//https://raw.githubusercontent.com/GFoley83/JsonFlatten/master/JsonFlatten/JsonExtensions.cs | ||
public static class JsonFlattenExtensions | ||
{ | ||
/// <summary> | ||
/// Flattens a JObject to a Dictionary.<c>null</c>, <c>""</c>, <c>[]</c> and <c>{}</c> are preserved by default | ||
/// </summary> | ||
/// <param name="jsonObject">JObject to flatten</param> | ||
/// <param name="includeNullAndEmptyValues">Set to false to ignore JSON properties that are null, "", [] and {} when flattening</param> | ||
public static IDictionary<string, object> Flatten(this JObject jsonObject, bool includeNullAndEmptyValues = true) => jsonObject | ||
.Descendants() | ||
.Where(p => !p.Any()) | ||
.Aggregate(new Dictionary<string, object>(), (properties, jToken) => | ||
{ | ||
var value = (jToken as JValue)?.Value; | ||
if (!includeNullAndEmptyValues) | ||
{ | ||
if (value?.Equals("") == false) | ||
{ | ||
properties.Add(jToken.Path, value); | ||
} | ||
return properties; | ||
} | ||
var strVal = jToken.Value<object>()?.ToString().Trim(); | ||
if (strVal?.Equals("[]") == true) | ||
{ | ||
value = Enumerable.Empty<object>(); | ||
} | ||
else if (strVal?.Equals("{}") == true) | ||
{ | ||
value = new object(); | ||
} | ||
properties.Add(jToken.Path, value); | ||
return properties; | ||
}); | ||
|
||
/// <summary> | ||
/// Unflattens an already flattened JSON Dictionary to its original JSON structure | ||
/// </summary> | ||
/// <param name="flattenedJsonKeyValues">Dictionary to unflatten</param> | ||
public static JObject Unflatten(this IDictionary<string, object> flattenedJsonKeyValues) | ||
{ | ||
JContainer result = null; | ||
var setting = new JsonMergeSettings | ||
{ | ||
MergeArrayHandling = MergeArrayHandling.Merge | ||
}; | ||
|
||
foreach (var pathValue in flattenedJsonKeyValues) | ||
{ | ||
if (result == null) | ||
{ | ||
result = UnflattenSingle(pathValue); | ||
} | ||
else | ||
{ | ||
result.Merge(UnflattenSingle(pathValue), setting); | ||
} | ||
} | ||
return result as JObject; | ||
} | ||
|
||
/// <summary> | ||
/// Get an item from the dictionary and cast it to a type. | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
/// <param name="dictionary"></param> | ||
/// <param name="key"></param> | ||
/// <returns></returns> | ||
public static T Get<T>(this IDictionary<string, object> dictionary, string key) => (T)dictionary[key]; | ||
|
||
/// <summary> | ||
/// Update an item in the dictionary | ||
/// </summary> | ||
/// <param name="dictionary"></param> | ||
/// <param name="key"></param> | ||
/// <param name="value"></param> | ||
public static void Set(this IDictionary<string, object> dictionary, string key, object value) => dictionary[key] = value; | ||
|
||
/// <summary> | ||
/// Try get an item from the dictionary and cast it to a type. | ||
/// </summary> | ||
/// <typeparam name="T"></typeparam> | ||
/// <param name="dictionary"></param> | ||
/// <param name="key"></param> | ||
/// <param name="value"></param> | ||
/// <returns></returns> | ||
public static bool TryGet<T>(this IDictionary<string, object> dictionary, string key, out T value) | ||
{ | ||
object result; | ||
if (dictionary.TryGetValue(key, out result) && result is T) | ||
{ | ||
value = (T)result; | ||
return true; | ||
} | ||
value = default(T); | ||
return false; | ||
} | ||
|
||
private static JContainer UnflattenSingle(KeyValuePair<string, object> keyValue) | ||
{ | ||
var path = keyValue.Key; | ||
JToken value = keyValue.Value != null ? JToken.FromObject(keyValue.Value) : null; | ||
var pathSegments = SplitPath(path); | ||
|
||
JContainer lastItem = null; | ||
//build from leaf to root | ||
foreach (var pathSegment in pathSegments.Reverse()) | ||
{ | ||
if (!IsJsonArray(pathSegment)) | ||
{ | ||
var obj = new JObject(); | ||
if (lastItem == null) | ||
{ | ||
obj.Add(pathSegment, value); | ||
} | ||
else | ||
{ | ||
obj.Add(pathSegment, lastItem); | ||
} | ||
lastItem = obj; | ||
|
||
continue; | ||
} | ||
|
||
var array = new JArray(); | ||
var index = GetArrayIndex(pathSegment); | ||
array = FillEmpty(array, index); | ||
array[index] = lastItem ?? value; | ||
lastItem = array; | ||
|
||
} | ||
return lastItem; | ||
} | ||
|
||
private static IList<string> SplitPath(string path) | ||
{ | ||
var result = new List<string>(); | ||
var reg = new Regex(@"(?!\.)([^. ^\[\]]+)|(?!\[)(\d+)(?=\])"); | ||
foreach (Match match in reg.Matches(path)) | ||
{ | ||
result.Add(match.Value); | ||
} | ||
return result; | ||
} | ||
|
||
private static JArray FillEmpty(JArray array, int index) | ||
{ | ||
for (var i = 0; i <= index; i++) | ||
{ | ||
array.Add(null); | ||
} | ||
return array; | ||
} | ||
|
||
private static bool IsJsonArray(string pathSegment) => int.TryParse(pathSegment, out var x); | ||
|
||
private static int GetArrayIndex(string pathSegment) | ||
{ | ||
if (int.TryParse(pathSegment, out var result)) | ||
{ | ||
return result; | ||
} | ||
throw new Exception($"Unable to parse array index: {pathSegment}"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
<Window x:Class="DataToolChain.JsonToMsSqlQuery" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
xmlns:local="clr-namespace:DataToolChain" | ||
mc:Ignorable="d" | ||
d:DataContext="{d:DesignInstance Type=local:RegexMatcherViewModel, IsDesignTimeCreatable=True}" | ||
Title="JsonFormatter" Height="650" Width="800"> | ||
<Grid> | ||
<Grid.ColumnDefinitions> | ||
<ColumnDefinition Width="395*"/> | ||
<ColumnDefinition Width="5"/> | ||
<ColumnDefinition Width="424*"/> | ||
</Grid.ColumnDefinitions> | ||
<Grid.RowDefinitions> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="*"/> | ||
</Grid.RowDefinitions> | ||
|
||
<StackPanel Background="Beige" Grid.ColumnSpan="3"> | ||
<TextBlock TextWrapping="Wrap" Margin="5">Enter Json on the left, formatted text appears on right.</TextBlock> | ||
|
||
</StackPanel> | ||
|
||
<GridSplitter Grid.Column="1" Grid.Row="1" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter> | ||
|
||
<Grid Grid.Column="0" Row="1"> | ||
<TextBox AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Path=StringInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox> | ||
</Grid> | ||
|
||
<TextBox AcceptsReturn="True" Grid.Row="1" Grid.Column="2" AcceptsTab="True" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Path=StringOutput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox> | ||
|
||
<!--<Grid Grid.Column="0" Grid.Row="1"> | ||
<Grid.RowDefinitions> | ||
<RowDefinition Height="Auto"/> | ||
<RowDefinition Height="5"/> | ||
<RowDefinition Height="410*"/> | ||
</Grid.RowDefinitions> | ||
<TextBox AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Path=RegexMatchInputs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox> | ||
<GridSplitter Grid.Column="1" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter> | ||
</Grid>--> | ||
|
||
</Grid> | ||
</Window> |
104 changes: 104 additions & 0 deletions
104
DataToolChain.Ui/JsonToMsSqlQuery/JsonToMsSqlQuery.xaml.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.Linq; | ||
using System.Runtime.CompilerServices; | ||
using System.Windows; | ||
using DataToolChain.Ui.Extensions; | ||
using Newtonsoft.Json.Linq; | ||
|
||
namespace DataToolChain | ||
{ | ||
/// <summary> | ||
/// This turns a JSON object and flattens it into a MS SQL query to use for JSON_VALUE querying. | ||
/// </summary> | ||
public partial class JsonToMsSqlQuery : Window | ||
{ | ||
public JsonToMsSqlQueryViewModel _viewModel { get; set; } = new JsonToMsSqlQueryViewModel(); | ||
|
||
public JsonToMsSqlQuery() | ||
{ | ||
InitializeComponent(); | ||
|
||
this.DataContext = _viewModel; | ||
} | ||
} | ||
|
||
|
||
/// <summary> | ||
/// | ||
/// </summary> | ||
public class JsonToMsSqlQueryViewModel : INotifyPropertyChanged | ||
{ | ||
private string _stringOutput; | ||
private string _stringInput = @"{ 'some': { 'sort': 'of', 'json': true } }"; | ||
|
||
public event PropertyChangedEventHandler PropertyChanged; | ||
|
||
private bool _addCasts = true; | ||
|
||
|
||
public string StringInput | ||
{ | ||
get { return _stringInput; } | ||
set | ||
{ | ||
_stringInput = value; | ||
UpdateOutput(); | ||
} | ||
} | ||
|
||
public string StringOutput | ||
{ | ||
get { return _stringOutput; } | ||
set | ||
{ | ||
_stringOutput = value; | ||
OnPropertyChanged(); | ||
} | ||
} | ||
|
||
public JsonToMsSqlQueryViewModel() | ||
{ | ||
UpdateOutput(); | ||
} | ||
|
||
public void UpdateOutput() | ||
{ | ||
try | ||
{ | ||
var dd = JObject.Parse(StringInput); | ||
|
||
var o = dd.Flatten() | ||
.Select(p => | ||
{ | ||
var jsonValStr = $"JSON_VALUE([value], '$.{p.Key}')"; | ||
var colAliasStr = $" AS [{p.Key}]"; | ||
if (_addCasts) | ||
{ | ||
if (double.TryParse(p.Value.ToString(), out double d)) | ||
jsonValStr = $"CAST({jsonValStr} AS FLOAT)"; | ||
} | ||
return $"{jsonValStr}{colAliasStr}"; | ||
}); | ||
|
||
var selectStr = "SELECT \r\n" + string.Join(",\r\n", o).Indent(); | ||
|
||
StringOutput = selectStr; | ||
} | ||
catch (Exception) | ||
{ | ||
StringOutput = "Error in Json."; | ||
return; | ||
} | ||
} | ||
|
||
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) | ||
{ | ||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | ||
} | ||
} | ||
} | ||
|