Skip to content

Commit

Permalink
Add json to ms sql query
Browse files Browse the repository at this point in the history
  • Loading branch information
nh43de committed Mar 13, 2023
1 parent 0a31cd4 commit 56b30e2
Show file tree
Hide file tree
Showing 4 changed files with 345 additions and 0 deletions.
16 changes: 16 additions & 0 deletions DataToolChain.Ui/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace DataToolChain.Ui.Extensions
{
public static class StringExtensions
{
public static readonly Regex IndentRegex = new Regex("^", RegexOptions.Compiled | RegexOptions.Multiline);

//
// Summary:
// Shorthand for string.Join(separator, enumerable)
Expand All @@ -19,5 +22,18 @@ public static string JoinStr<T>(this IEnumerable<T> enumerable, string separator
{
return string.Join(separator, enumerable);
}


/// <summary>
/// Indent each line using tabs.
/// </summary>
/// <param name="str"></param>
/// <param name="tabs"></param>
/// <returns></returns>
public static string Indent(this string str, int tabs = 1)
{
return IndentRegex.Replace(str, "".PadLeft(tabs, '\t'));
}

}
}
179 changes: 179 additions & 0 deletions DataToolChain.Ui/JsonToMsSqlQuery/JsonFlattenExtensions.cs
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}");
}
}
}
46 changes: 46 additions & 0 deletions DataToolChain.Ui/JsonToMsSqlQuery/JsonToMsSqlQuery.xaml
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 DataToolChain.Ui/JsonToMsSqlQuery/JsonToMsSqlQuery.xaml.cs
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));
}
}
}

0 comments on commit 56b30e2

Please sign in to comment.