Skip to content

Commit 56b30e2

Browse files
committed
Add json to ms sql query
1 parent 0a31cd4 commit 56b30e2

File tree

4 files changed

+345
-0
lines changed

4 files changed

+345
-0
lines changed

DataToolChain.Ui/Extensions/StringExtensions.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
using System.Collections.Generic;
2+
using System.Text.RegularExpressions;
23

34
namespace DataToolChain.Ui.Extensions
45
{
56
public static class StringExtensions
67
{
8+
public static readonly Regex IndentRegex = new Regex("^", RegexOptions.Compiled | RegexOptions.Multiline);
9+
710
//
811
// Summary:
912
// Shorthand for string.Join(separator, enumerable)
@@ -19,5 +22,18 @@ public static string JoinStr<T>(this IEnumerable<T> enumerable, string separator
1922
{
2023
return string.Join(separator, enumerable);
2124
}
25+
26+
27+
/// <summary>
28+
/// Indent each line using tabs.
29+
/// </summary>
30+
/// <param name="str"></param>
31+
/// <param name="tabs"></param>
32+
/// <returns></returns>
33+
public static string Indent(this string str, int tabs = 1)
34+
{
35+
return IndentRegex.Replace(str, "".PadLeft(tabs, '\t'));
36+
}
37+
2238
}
2339
}
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text.RegularExpressions;
5+
using Newtonsoft.Json.Linq;
6+
7+
namespace DataToolChain
8+
{
9+
10+
//https://raw.githubusercontent.com/GFoley83/JsonFlatten/master/JsonFlatten/JsonExtensions.cs
11+
public static class JsonFlattenExtensions
12+
{
13+
/// <summary>
14+
/// Flattens a JObject to a Dictionary.<c>null</c>, <c>""</c>, <c>[]</c> and <c>{}</c> are preserved by default
15+
/// </summary>
16+
/// <param name="jsonObject">JObject to flatten</param>
17+
/// <param name="includeNullAndEmptyValues">Set to false to ignore JSON properties that are null, "", [] and {} when flattening</param>
18+
public static IDictionary<string, object> Flatten(this JObject jsonObject, bool includeNullAndEmptyValues = true) => jsonObject
19+
.Descendants()
20+
.Where(p => !p.Any())
21+
.Aggregate(new Dictionary<string, object>(), (properties, jToken) =>
22+
{
23+
var value = (jToken as JValue)?.Value;
24+
25+
if (!includeNullAndEmptyValues)
26+
{
27+
if (value?.Equals("") == false)
28+
{
29+
properties.Add(jToken.Path, value);
30+
}
31+
return properties;
32+
}
33+
34+
var strVal = jToken.Value<object>()?.ToString().Trim();
35+
if (strVal?.Equals("[]") == true)
36+
{
37+
value = Enumerable.Empty<object>();
38+
}
39+
else if (strVal?.Equals("{}") == true)
40+
{
41+
value = new object();
42+
}
43+
44+
properties.Add(jToken.Path, value);
45+
46+
return properties;
47+
});
48+
49+
/// <summary>
50+
/// Unflattens an already flattened JSON Dictionary to its original JSON structure
51+
/// </summary>
52+
/// <param name="flattenedJsonKeyValues">Dictionary to unflatten</param>
53+
public static JObject Unflatten(this IDictionary<string, object> flattenedJsonKeyValues)
54+
{
55+
JContainer result = null;
56+
var setting = new JsonMergeSettings
57+
{
58+
MergeArrayHandling = MergeArrayHandling.Merge
59+
};
60+
61+
foreach (var pathValue in flattenedJsonKeyValues)
62+
{
63+
if (result == null)
64+
{
65+
result = UnflattenSingle(pathValue);
66+
}
67+
else
68+
{
69+
result.Merge(UnflattenSingle(pathValue), setting);
70+
}
71+
}
72+
return result as JObject;
73+
}
74+
75+
/// <summary>
76+
/// Get an item from the dictionary and cast it to a type.
77+
/// </summary>
78+
/// <typeparam name="T"></typeparam>
79+
/// <param name="dictionary"></param>
80+
/// <param name="key"></param>
81+
/// <returns></returns>
82+
public static T Get<T>(this IDictionary<string, object> dictionary, string key) => (T)dictionary[key];
83+
84+
/// <summary>
85+
/// Update an item in the dictionary
86+
/// </summary>
87+
/// <param name="dictionary"></param>
88+
/// <param name="key"></param>
89+
/// <param name="value"></param>
90+
public static void Set(this IDictionary<string, object> dictionary, string key, object value) => dictionary[key] = value;
91+
92+
/// <summary>
93+
/// Try get an item from the dictionary and cast it to a type.
94+
/// </summary>
95+
/// <typeparam name="T"></typeparam>
96+
/// <param name="dictionary"></param>
97+
/// <param name="key"></param>
98+
/// <param name="value"></param>
99+
/// <returns></returns>
100+
public static bool TryGet<T>(this IDictionary<string, object> dictionary, string key, out T value)
101+
{
102+
object result;
103+
if (dictionary.TryGetValue(key, out result) && result is T)
104+
{
105+
value = (T)result;
106+
return true;
107+
}
108+
value = default(T);
109+
return false;
110+
}
111+
112+
private static JContainer UnflattenSingle(KeyValuePair<string, object> keyValue)
113+
{
114+
var path = keyValue.Key;
115+
JToken value = keyValue.Value != null ? JToken.FromObject(keyValue.Value) : null;
116+
var pathSegments = SplitPath(path);
117+
118+
JContainer lastItem = null;
119+
//build from leaf to root
120+
foreach (var pathSegment in pathSegments.Reverse())
121+
{
122+
if (!IsJsonArray(pathSegment))
123+
{
124+
var obj = new JObject();
125+
if (lastItem == null)
126+
{
127+
obj.Add(pathSegment, value);
128+
}
129+
else
130+
{
131+
obj.Add(pathSegment, lastItem);
132+
}
133+
lastItem = obj;
134+
135+
continue;
136+
}
137+
138+
var array = new JArray();
139+
var index = GetArrayIndex(pathSegment);
140+
array = FillEmpty(array, index);
141+
array[index] = lastItem ?? value;
142+
lastItem = array;
143+
144+
}
145+
return lastItem;
146+
}
147+
148+
private static IList<string> SplitPath(string path)
149+
{
150+
var result = new List<string>();
151+
var reg = new Regex(@"(?!\.)([^. ^\[\]]+)|(?!\[)(\d+)(?=\])");
152+
foreach (Match match in reg.Matches(path))
153+
{
154+
result.Add(match.Value);
155+
}
156+
return result;
157+
}
158+
159+
private static JArray FillEmpty(JArray array, int index)
160+
{
161+
for (var i = 0; i <= index; i++)
162+
{
163+
array.Add(null);
164+
}
165+
return array;
166+
}
167+
168+
private static bool IsJsonArray(string pathSegment) => int.TryParse(pathSegment, out var x);
169+
170+
private static int GetArrayIndex(string pathSegment)
171+
{
172+
if (int.TryParse(pathSegment, out var result))
173+
{
174+
return result;
175+
}
176+
throw new Exception($"Unable to parse array index: {pathSegment}");
177+
}
178+
}
179+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<Window x:Class="DataToolChain.JsonToMsSqlQuery"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:local="clr-namespace:DataToolChain"
7+
mc:Ignorable="d"
8+
d:DataContext="{d:DesignInstance Type=local:RegexMatcherViewModel, IsDesignTimeCreatable=True}"
9+
Title="JsonFormatter" Height="650" Width="800">
10+
<Grid>
11+
<Grid.ColumnDefinitions>
12+
<ColumnDefinition Width="395*"/>
13+
<ColumnDefinition Width="5"/>
14+
<ColumnDefinition Width="424*"/>
15+
</Grid.ColumnDefinitions>
16+
<Grid.RowDefinitions>
17+
<RowDefinition Height="Auto"/>
18+
<RowDefinition Height="*"/>
19+
</Grid.RowDefinitions>
20+
21+
<StackPanel Background="Beige" Grid.ColumnSpan="3">
22+
<TextBlock TextWrapping="Wrap" Margin="5">Enter Json on the left, formatted text appears on right.</TextBlock>
23+
24+
</StackPanel>
25+
26+
<GridSplitter Grid.Column="1" Grid.Row="1" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter>
27+
28+
<Grid Grid.Column="0" Row="1">
29+
<TextBox AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Path=StringInput, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
30+
</Grid>
31+
32+
<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>
33+
34+
<!--<Grid Grid.Column="0" Grid.Row="1">
35+
<Grid.RowDefinitions>
36+
<RowDefinition Height="Auto"/>
37+
<RowDefinition Height="5"/>
38+
<RowDefinition Height="410*"/>
39+
</Grid.RowDefinitions>
40+
<TextBox AcceptsReturn="True" AcceptsTab="True" TextWrapping="NoWrap" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Text="{Binding Path=RegexMatchInputs, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></TextBox>
41+
<GridSplitter Grid.Column="1" Background="Gray" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"></GridSplitter>
42+
43+
</Grid>-->
44+
45+
</Grid>
46+
</Window>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Linq;
4+
using System.Runtime.CompilerServices;
5+
using System.Windows;
6+
using DataToolChain.Ui.Extensions;
7+
using Newtonsoft.Json.Linq;
8+
9+
namespace DataToolChain
10+
{
11+
/// <summary>
12+
/// This turns a JSON object and flattens it into a MS SQL query to use for JSON_VALUE querying.
13+
/// </summary>
14+
public partial class JsonToMsSqlQuery : Window
15+
{
16+
public JsonToMsSqlQueryViewModel _viewModel { get; set; } = new JsonToMsSqlQueryViewModel();
17+
18+
public JsonToMsSqlQuery()
19+
{
20+
InitializeComponent();
21+
22+
this.DataContext = _viewModel;
23+
}
24+
}
25+
26+
27+
/// <summary>
28+
///
29+
/// </summary>
30+
public class JsonToMsSqlQueryViewModel : INotifyPropertyChanged
31+
{
32+
private string _stringOutput;
33+
private string _stringInput = @"{ 'some': { 'sort': 'of', 'json': true } }";
34+
35+
public event PropertyChangedEventHandler PropertyChanged;
36+
37+
private bool _addCasts = true;
38+
39+
40+
public string StringInput
41+
{
42+
get { return _stringInput; }
43+
set
44+
{
45+
_stringInput = value;
46+
UpdateOutput();
47+
}
48+
}
49+
50+
public string StringOutput
51+
{
52+
get { return _stringOutput; }
53+
set
54+
{
55+
_stringOutput = value;
56+
OnPropertyChanged();
57+
}
58+
}
59+
60+
public JsonToMsSqlQueryViewModel()
61+
{
62+
UpdateOutput();
63+
}
64+
65+
public void UpdateOutput()
66+
{
67+
try
68+
{
69+
var dd = JObject.Parse(StringInput);
70+
71+
var o = dd.Flatten()
72+
.Select(p =>
73+
{
74+
var jsonValStr = $"JSON_VALUE([value], '$.{p.Key}')";
75+
76+
var colAliasStr = $" AS [{p.Key}]";
77+
78+
if (_addCasts)
79+
{
80+
if (double.TryParse(p.Value.ToString(), out double d))
81+
jsonValStr = $"CAST({jsonValStr} AS FLOAT)";
82+
}
83+
84+
return $"{jsonValStr}{colAliasStr}";
85+
});
86+
87+
var selectStr = "SELECT \r\n" + string.Join(",\r\n", o).Indent();
88+
89+
StringOutput = selectStr;
90+
}
91+
catch (Exception)
92+
{
93+
StringOutput = "Error in Json.";
94+
return;
95+
}
96+
}
97+
98+
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
99+
{
100+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
101+
}
102+
}
103+
}
104+

0 commit comments

Comments
 (0)