The following javascript question is the same problem i'm attempting to solve but in c#
How can I merge 2 dot notation strings to a GraphQL query string
Expected structure is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw":{
"Name": null
},
"CaseType": {
"Name": null
},
"CaseSubType": {
"Name": null
},
},
"Client":{
"Policy":{
"PolicyNo": null
}
}
}
and my current output is
{
"Case": {
"Owner": {
"Name": null,
"ProfilePic": null
},
"CaseNo": null,
"FieldOfLaw": null,
"CaseType": null,
"CaseSubType": null
}
}
Below is my attempt using ExpandoObjects to try dynamically generate the objects needed. Any advice or pointers in the right direction would be appreciated.
public static void Main(string[] args)
{
var FieldList = new List<string>
{
"Case.Owner.Name",
"Case.Owner.ProfilePic",
"Case.CaseNo",
"Case.FieldOfLaw.Name",
"Case.CaseType.Name",
"Case.CaseSubType.Name",
"Client.Policy.PolicyNo",
};
Parser graphQL = new Parser();
var result = graphQL.Parse(FieldList);
Console.Write(result);
}
Below is the actual parse method, so i'm running an aggregation function over each element to create and return the expando objects into the initial holder. I traverse each split string recursively and exit the recursion once there are no more items left in the split list.
public class Parser
{
public string Parse(List<string> fieldList)
{
// List<ExpandoObject> queryHolder = new List<ExpandoObject>();
ExpandoObject intialSeed = new ExpandoObject();
fieldList.Aggregate(intialSeed, (holder, field) =>
{
holder = ParseToObject(holder, field.Split('.').ToList());
return holder;
});
return JsonConvert.SerializeObject(intialSeed);
}
public ExpandoObject ParseToObject(ExpandoObject holder, List<string> fieldSplit, string previousKey = null)
{
if (fieldSplit.Any())
{
var item = fieldSplit.Shift();
if (item == null)
return holder;
// If the current item doesn't exists in the dictionary
if (!((IDictionary<string, object>)holder).ContainsKey(item))
{
if (((IDictionary<string, object>)holder).Keys.Count() == 0)
holder.TryAdd(item, null);
else
_ = ((IDictionary<string, object>)holder).GetItemByKeyRecursively(previousKey, item);
}
previousKey = item;
ParseToObject(holder, fieldSplit, previousKey);
}
return holder;
}
}
Here are my two extensions methods, i'm having an issue with the GetItemByKeyRecursively when it goes into the 3rd level in it's recursion so example.
I'm adding FieldOfLaw it adds the property to the Case expandoObject but doesn't know how to get back to the leaf containing Owner, CaseNo etc.
public static class CollectionExtensions
{
public static T Shift<T>(this IList<T> list)
{
var shiftedElement = list.FirstOrDefault();
list.RemoveAt(0);
return shiftedElement;
}
public static IDictionary<string, object> GetItemByKeyRecursively(this IDictionary<string, object> dictionary, string parentKey, string keyToCreate)
{
foreach (string key in dictionary.Keys)
{
var leaf = dictionary[key];
if (key == parentKey)
{
var #value = dictionary[key];
if (#value is ExpandoObject)
{
(#value as ExpandoObject).TryAdd(keyToCreate, null);
}
else if (#value == null)
{
var item = new ExpandoObject();
item.TryAdd(keyToCreate, null);
dictionary[key] = item;
}
return dictionary;
}
if (leaf == null)
continue;
return GetItemByKeyRecursively((IDictionary<string, object>)leaf, parentKey, keyToCreate);
}
return null;
}
}
Nothing you can't accomplish mostly declaratively.
public string Parse(List<string> fieldList)
{
var fieldPaths = fieldList.Select(x => x.Split('.').ToList());
var groups = fieldPaths.GroupBy(x => x.First(), x => x.Skip(1));
return ParseGroups(groups, 1);
}
private string ParseGroups(IEnumerable<IGrouping<string, IEnumerable<string>>> groups, int level)
{
string indent = new string('\t', level - 1);
var groupResults = groups.Select(g =>
!g.First().Any() ?
$"\t{indent}{g.Key}: null" :
$"\t{indent}{g.Key}: " + string.Join(", \n",
ParseGroups(g.GroupBy(x => x.First(), x => x.Skip(1)), level + 1))
);
return indent + "{\n" + string.Join(", \n", groupResults) + "\n" + indent + "}";
}
See the complete sample code here: https://dotnetfiddle.net/RLygjt
I am using reflection to get a class name, and need to get all sub properties of the class, and all the sub properties' properties.
I am running into a recursion issue where the items get added to the incorrect list.
My code is as follows:
private List<Member> GetMembers(object instance)
{
var memberList = new List<Member>();
var childMembers = new List<Member>();
foreach (var propertyInfo in instance.GetType().GetProperties())
{
var member = new Member
{
Name = propertyInfo.PropertyType.IsList() ? propertyInfo.Name + "[]" : propertyInfo.Name,
Type = SetPropertyType(propertyInfo.PropertyType),
};
if (propertyInfo.PropertyType.IsEnum)
{
member.Members = GetEnumValues(propertyInfo).ToArray();
}
if (propertyInfo.PropertyType.BaseType == typeof(ModelBase))
{
var childInstance = propertyInfo.GetValue(instance) ?? Activator.CreateInstance(propertyInfo.PropertyType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.ToArray();
}
if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) ||
propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
{
var itemType = propertyInfo.PropertyType.GetGenericArguments()[0];
var childInstance = Activator.CreateInstance(itemType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.Distinct().ToArray();
}
memberList.Add(member);
}
return memberList;
}
I can't know for certain since I don't have the knowledge of your code to debug and test it; however, I believe your problem may be stemming from the fact that you're re-using the childMembers list. Let me know if this is not the case.
private List<Member> GetMembers(object instance)
{
var memberList = new List<Member>();
foreach (var propertyInfo in instance.GetType().GetProperties())
{
var childMembers = new List<Member>(); // Moved to here, so it's not shared among all propertyInfo iterations.
var member = new Member
{
Name = propertyInfo.PropertyType.IsList() ? propertyInfo.Name + "[]" : propertyInfo.Name,
Type = SetPropertyType(propertyInfo.PropertyType),
};
if (propertyInfo.PropertyType.IsEnum)
{
member.Members = GetEnumValues(propertyInfo).ToArray();
}
if (propertyInfo.PropertyType.BaseType == typeof(ModelBase))
{
var childInstance = propertyInfo.GetValue(instance) ?? Activator.CreateInstance(propertyInfo.PropertyType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.ToArray();
}
if (propertyInfo.PropertyType.IsGenericType && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(List<>) ||
propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
{
var itemType = propertyInfo.PropertyType.GetGenericArguments()[0];
var childInstance = Activator.CreateInstance(itemType);
childMembers.AddRange(GetMembers(childInstance));
member.Members = childMembers.Distinct().ToArray();
}
memberList.Add(member);
}
return memberList;
}
Wouldn't the following do?
public static IEnumerable<PropertyInfo> GetProperties(this Type type, int depth = 1)
{
IEnumerable<PropertyInfo> getProperties(Type currentType, int currentDepth)
{
if (currentDepth >= depth)
yield break;
foreach (var property in currentType.GetProperties())
{
yield return property;
foreach (var subProperty in getProperties(property.PropertyType,
currentDepth + 1))
{
yield return subProperty;
}
}
}
if (depth < 1)
throw new ArgumentOutOfRangeException(nameof(depth));
return getProperties(type, 0);
}
Given the following type:
class Foo
{
public string S { get; }
public int I { get; }
}
The output of
Console.WriteLine(string.Join(Environment.NewLine,
typeof(Foo).GetProperties(2)
.Select(p => $"{p.DeclaringType.Name}: {p.Name}")));
would be:
Foo: S
String: Chars
String: Length
Foo: I
I writing app for UWP
I have this code
var p = await wc.GetProducts(new Dictionary<string, string>() {
{ "orderby", "id" }, { "filter[search]", "1884" }
});
I try to display data from dictionary like this.
Debug.WriteLine("There");
Debug.WriteLine(p.products);
But it not works.
How I can display data of dictionary ?
Which type does GetProducts() return?
If it's just a Dictionary you can do the following:
foreach(var key in p.Keys)
{
Debug.WriteLine(key);
}
To read a Dictionary --
foreach(KeyValuePair<string,string> kvp in p){
Console.WriteLine(kvp.Key);
Console.WriteLine(kvp.Value);
}
foreach(string key in p.Keys){
Console.WriteLine(key);
Console.WriteLine(p[key]);//value
}
foreach(string value in p.Values){
Console.WriteLine(value);
}
But your problem is that P is a class called products:
Product p = new Product()
{
name = "test product 8",
title = "test product 8",
description = "test product 8",
price = 8.0M
};
You would access the properties of p like:
p.name;
p.title;
p.description;
p.price;
Debug.WriteLine(p.name);
Debug.WriteLine(p.title);//etc
Using the below extension class (Requires Newtonsoft JSON library) you can get a JSON string of any object either with or without readable formatting.
Using the class to get a readable JSON string;
var p = await wc.GetProducts(new Dictionary<string, string>() {
{ "orderby", "id" }, { "filter[search]", "1884" }
});
var jsonString = p.ToFormattedJsonString();
Debug.WriteLine(jsonString);
Using the class to get a plain JSON string without format;
var p = await wc.GetProducts(new Dictionary<string, string>() {
{ "orderby", "id" }, { "filter[search]", "1884" }
});
var jsonString = p.ToJsonString();
Debug.WriteLine(jsonString);
You can also simplfy the above by adding your own extension method such as the below;
public static void ToDebug(this object data)
{
Debug.WriteLine(data.ToFormattedJsonString());
}
The extension class;
using System.Text;
using Newtonsoft.Json;
namespace System
{
public static class JsonExtensions
{
public static string ToFormattedJsonString(this object obj, bool indentWithTab)
{
return indentWithTab
? ToFormattedJsonString(obj, "\t")
: ToFormattedJsonString(obj);
}
public static string ToFormattedJsonString(this object obj, string indentString = " ")
{
return FormatJson(obj.ToJsonString(), indentString);
}
public static string ToJsonString(this object obj)
{
return JsonConvert.SerializeObject(obj);
}
public static T DeserializeJsonString<T>(this string jsonString)
{
return JsonConvert.DeserializeObject<T>(jsonString);
}
private static string FormatJson(string jsonString, string indentString)
{
var indent = 0;
var quoted = false;
var builder = new StringBuilder();
for (var i = 0; i < jsonString.Length; i++)
{
var character = jsonString[i];
switch (character)
{
case '{':
case '[':
builder.Append(character);
if (!quoted)
{
builder.AppendLine();
builder.RepeatAppend(++indent, indentString);
}
break;
case '}':
case ']':
if (!quoted)
{
builder.AppendLine();
builder.RepeatAppend(--indent, indentString);
}
builder.Append(character);
break;
case '"':
builder.Append(character);
bool escaped = false;
var index = i;
while (index > 0 && jsonString[--index] == '\\')
escaped = !escaped;
if (!escaped)
quoted = !quoted;
break;
case ',':
builder.Append(character);
if (!quoted)
{
builder.AppendLine();
builder.RepeatAppend(indent, indentString);
}
break;
case ':':
builder.Append(character);
if (!quoted)
builder.Append(" ");
break;
default:
builder.Append(character);
break;
}
}
return builder.ToString();
}
public static StringBuilder RepeatAppend(this StringBuilder builder, int count, string format,
params object[] parameters)
{
if (count <= 0 || string.IsNullOrEmpty(format))
return builder;
for (int i = 0; i < count; i++)
{
builder.AppendFormat(format, parameters);
}
return builder;
}
}
}
I am working with a cut down version of C# 3.5 on a platform that doesn't have the flexibility of using third party libraries.
While I can parse JSON (using a json stream reader), I am not sure how to actually turn it into a class. (there is also no access to the usual json to class deserializer).
Does anyone know how to use reflection to manually (yet dynamically) turn a JSON string into a class?
sample Json:
{"items":[
{"firstName":"bob", "lastName":"smith", "id":1001, "foods": [{"name":"fish", "name":"bacon", "name":"cereal"}]},
{"firstName":"sarah", "lastName":"smith", "id":1002, "foods": [{"name":"bacon", "name":"apples", "name":"chocolate"}]},
{"firstName":"tom", "lastName":"waffle", "id":1003, "foods": [{"name":"waffles", "name":"sticks", "name":"stones"}]},
{"firstName":"reginald", "lastName":"hamtuft", "id":1003, "foods": [{"name":"ham", "name":"cigars", "name":"noisy children"}]}
]}
Thanks Pete and the rest for getting me on the right track for this. In my case I also had to deserialize a JSON string to a Strongly Typed object in a SQL CLR Function, so I was limited on the libraries I could use "safely" (more info here).
I modified the code for ParseJSON, which deserializes into a Dictionary<string, object> to be able to deserialize arrays of arrays, which it couldn't do, I also developed some methods to cast the resulting Dictionary into a Strongly Typed Object without using the JavaScriptConverter or the System.Runtime.Serialization library, with this code we are able to do the following:
//we have a foo and bar classes with a variety of fields and properties
private class foo
{
public List<double[][]> data;
public IEnumerable<object> DataObj;
public int integerField;
public long longProperty { get; set; }
public string stringValue;
public int? nullableInt;
public DateTime dateTimeValue;
public List<bar> classValues;
}
private class bar
{
public string stringValue;
public DateTimeOffset dateTimeOffsetValue;
}
static void Main(string[] args)
{
//lets deserialize the following JSON string into our foo object,
//the dictionary is optional, and not necessary if our JSON property names are the same as in our object.
//in this case it's used to map the "jdata" property on the JSON string to the "data" property of our object,
//in the case of the "dataObj", we are mapping to the uppercase field of our object
string JSONstring = "{\"jdata\":[[[1526518800000,7.0],[1526518834200,7.0]],[[1526549272200,25.0],[1526549306400,25.0]]],\"dataObj\":[[[1526518800000,7.0],[1526518834200,7.0]],\"abc\",123],\"integerField\":623,\"longProperty\":456789,\"stringValue\":\"foo\",\"nullableInt\":\"\",\"dateTimeValue\":\"2018-05-17T01:00:00.0000000\", \"classValues\": [{\"stringValue\":\"test\",\"dateTimeOffsetValue\":\"2018-05-17T05:00:00.0000000\"},{\"stringValue\":\"test2\",\"dateTimeOffsetValue\":\"2018-05-17T06:00:00.0000000\"}]}";
var mappingDict = new Dictionary<string, string>() { { "jdata", "data" }, { "dataObj", "DataObj" } };
foo myObject = ParseJSON<foo>(JSONstring, mappingDict);
}
The ParseJSON method will take a JSON string as input and optionally a Dictionary<string, string> and will attempt to cast it into the Type T the dictionary is used to map any property on the JSON string into a property of the object (for example, the "jdata"/"data" dictionary declared above).
public static T ParseJSON<T>(string jsonString, Dictionary<string, string> mappingTable = null)
{
Dictionary<string, object> jsonDictionary = ParseJSON(jsonString);
T castedObj = CastAs<T>(jsonDictionary, mappingTable);
return castedObj;
}
The following is my modified method for JSON parsing (can parse arrays of arrays):
public static Dictionary<string, object> ParseJSON(string json)
{
int end;
return ParseJSON(json, 0, out end);
}
private static Dictionary<string, object> ParseJSON(string json, int start, out int end)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
bool escbegin = false;
bool escend = false;
bool inquotes = false;
string key = null;
int cend;
StringBuilder sb = new StringBuilder();
Dictionary<string, object> child = null;
List<object> arraylist = null;
Regex regex = new Regex(#"\\u([0-9a-z]{4})", RegexOptions.IgnoreCase);
int autoKey = 0;
int subArrayCount = 0;
List<int> arrayIndexes = new List<int>();
bool inSingleQuotes = false;
bool inDoubleQuotes = false;
for (int i = start; i < json.Length; i++)
{
char c = json[i];
if (c == '\\') escbegin = !escbegin;
if (!escbegin)
{
if (c == '"' && !inSingleQuotes)
{
inDoubleQuotes = !inDoubleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
else if (c == '\'' && !inDoubleQuotes)
{
inSingleQuotes = !inSingleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
if (!inquotes)
{
switch (c)
{
case '{':
if (i != start)
{
child = ParseJSON(json, i, out cend);
if (arraylist != null)
{
arraylist.Add(child);
}
else
{
dict.Add(key.Trim(), child);
key = null;
}
i = cend;
}
continue;
case '}':
end = i;
if (key != null)
{
if (arraylist != null) dict.Add(key.Trim(), arraylist);
else dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
}
return dict;
case '[':
if (arraylist != null)
{
List<object> _tempArrayList = arraylist;
for (int l = 0; l < subArrayCount; l++)
{
if (l == subArrayCount - 1)
{
_tempArrayList.Add(new List<object>());
}
else
{
_tempArrayList = (List<object>)_tempArrayList[arrayIndexes[l]];
}
}
if (arrayIndexes.Count < subArrayCount)
{
arrayIndexes.Add(0);
}
subArrayCount++;
}
else
{
arraylist = new List<object>();
subArrayCount++;
}
continue;
case ']':
if (key == null)
{
key = "array" + autoKey.ToString();
autoKey++;
}
if (arraylist != null)
{
List<object> _tempArrayList = arraylist;
for (int l = 0; l < subArrayCount; l++)
{
if (l == subArrayCount - 1)
{
if (sb.Length > 0)
{
_tempArrayList.Add(sb.ToString());
}
subArrayCount--;
if (subArrayCount == arrayIndexes.Count)
{
if (arrayIndexes.Count > 0)
{
arrayIndexes[arrayIndexes.Count - 1]++;
}
}
else if (subArrayCount == arrayIndexes.Count - 1)
{
arrayIndexes.RemoveAt(arrayIndexes.Count - 1);
if (arrayIndexes.Count > 0)
{
arrayIndexes[arrayIndexes.Count - 1]++;
}
}
}
else
{
_tempArrayList = (List<object>)_tempArrayList[arrayIndexes[l]];
}
}
sb.Length = 0;
}
if (subArrayCount == 0)
{
dict.Add(key.Trim(), arraylist);
arraylist = null;
key = null;
}
continue;
case ',':
if (arraylist == null && key != null)
{
dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
key = null;
sb.Length = 0;
}
if (arraylist != null && sb.Length > 0)
{
List<object> _tempArrayList = arraylist;
for (int l = 0; l < subArrayCount; l++)
{
if (l == subArrayCount - 1)
{
_tempArrayList.Add(sb.ToString());
}
else
{
_tempArrayList = (List<object>)_tempArrayList[arrayIndexes[l]];
}
}
sb.Length = 0;
}
continue;
case ':':
key = DecodeString(regex, sb.ToString());
sb.Length = 0;
continue;
}
}
}
sb.Append(c);
if (escend) escbegin = false;
if (escbegin) escend = true;
else escend = false;
}
end = json.Length - 1;
return dict; //shouldn't ever get here unless the JSON is malformed
}
private static string DecodeString(Regex regex, string str)
{
return Regex.Unescape(regex.Replace(str, match => char.ConvertFromUtf32(Int32.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.HexNumber))));
}
The following methods attempt to cast the returned dictionary from the previous method into a Strong Typed Object, I know is lengthy but it does the job:
private static T CastAs<T>(Dictionary<string, object> source, Dictionary<string, string> mappingTable = null)
{
T outputData = (T)Activator.CreateInstance(typeof(T));
TrySet(outputData, source, mappingTable);
return outputData;
}
private static void TrySet(object target, Dictionary<string, object> source, Dictionary<string, string> mappingTable = null)
{
if (target == null)
{
throw new ArgumentNullException("target");
}
bool useMappingTable = mappingTable != null && mappingTable.Count > 0;
foreach (KeyValuePair<string, object> kv in source)
{
string propertyName = null;
if (useMappingTable && mappingTable.ContainsKey(kv.Key))
{
propertyName = mappingTable[kv.Key];
}
else
{
propertyName = kv.Key;
}
if (!string.IsNullOrEmpty(propertyName))
{
UpdateMember(target, propertyName, kv.Value, mappingTable);
}
}
}
private static void UpdateMember(object target, string propertyName, object value, Dictionary<string, string> mappingTable)
{
try
{
FieldInfo fieldInfo = target.GetType().GetField(propertyName);
if (fieldInfo != null)
{
value = ConvertTo(value, fieldInfo.FieldType, mappingTable);
fieldInfo.SetValue(target, value);
}
else
{
PropertyInfo propInfo = target.GetType().GetProperty(propertyName);
if (propInfo != null)
{
value = ConvertTo(value, propInfo.PropertyType, mappingTable);
propInfo.SetValue(target, value);
}
}
}
catch (Exception ex)
{
throw ex;
}
}
private static object ConvertTo(object value, Type targetType, Dictionary<string, string> mappingTable)
{
try
{
bool isNullable = false;
Type sourceType = value.GetType();
//Obtain actual type to convert to (this is necessary in case of Nullable types)
if (targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
isNullable = true;
targetType = targetType.GetGenericArguments()[0];
}
if (isNullable && string.IsNullOrWhiteSpace(Convert.ToString(value)))
{
return null;
}
//if we are converting from a dictionary to a class, call the TrySet method to convert its members
else if (targetType.IsClass && sourceType.IsGenericType && sourceType.GetGenericTypeDefinition() == typeof(Dictionary<,>))
{
//make sure our value is actually a Dictionary<string, object> in order to be able to cast
if (sourceType.GetGenericArguments()[0] == typeof(string))
{
object convertedValue = Activator.CreateInstance(targetType);
TrySet(convertedValue, (Dictionary<string, object>)value, mappingTable);
return convertedValue;
}
return null;
}
else if (IsCollection(value))
{
Type elementType = GetCollectionElementType(targetType);
if (elementType != null)
{
if (targetType.BaseType == typeof(Array))
{
return ConvertToArray(elementType, value, mappingTable);
}
else
{
return ConvertToList(elementType, value, mappingTable);
}
}
else
{
throw new NullReferenceException();
}
}
else if (targetType == typeof(DateTimeOffset))
{
return new DateTimeOffset((DateTime)ChangeType(value, typeof(DateTime)));
}
else if (targetType == typeof(object))
{
return value;
}
else
{
return ChangeType(value, targetType);
}
}
catch (Exception ex)
{
if (targetType.IsValueType)
{
return Activator.CreateInstance(targetType);
}
return null;
}
}
private static Array ConvertToArray(Type elementType, object value, Dictionary<string, string> mappingTable)
{
Array collection = Array.CreateInstance(elementType, ((ICollection)value).Count);
int i = 0;
foreach (object item in (IEnumerable)value)
{
try
{
collection.SetValue(ConvertTo(item, elementType, mappingTable), i);
i++;
}
catch (Exception ex)
{
//nothing here, just skip the item
}
}
return collection;
}
private static IList ConvertToList(Type elementType, object value, Dictionary<string, string> mappingTable)
{
Type listType = typeof(List<>);
Type constructedListType = listType.MakeGenericType(elementType);
IList collection = (IList)Activator.CreateInstance(constructedListType);
foreach (object item in (IEnumerable)value)
{
try
{
collection.Add(ConvertTo(item, elementType, mappingTable));
}
catch (Exception ex)
{
//nothing here, just skip the item
}
}
return collection;
}
private static bool IsCollection(object obj)
{
bool isCollection = false;
Type objType = obj.GetType();
if (!typeof(string).IsAssignableFrom(objType) && typeof(IEnumerable).IsAssignableFrom(objType))
{
isCollection = true;
}
return isCollection;
}
private static Type GetCollectionElementType(Type objType)
{
Type elementType;
Type[] genericArgs = objType.GenericTypeArguments;
if (genericArgs.Length > 0)
{
elementType = genericArgs[0];
}
else
{
elementType = objType.GetElementType();
}
return elementType;
}
private static object ChangeType(object value, Type castTo)
{
try
{
return Convert.ChangeType(value, castTo);
}
catch (Exception ex)
{
//if the conversion failed, just return the original value
return value;
}
}
I hope this is helpful to anyone still looking for a way to do this.
Okay, I'm redoing my answer based on the feedback. The dynamic object generator code still comes from this:
Deserialize JSON into C# dynamic object?
This uses RegEx, Generic collections and it does use Linq, but only in 2 lines and those can easily rewritten to not use Linq (the two 'result = ' lines at the end of DynamicJsonObject.TryGetMember()). The generic dictionaries can also be replaced with hash tables if necessary.
The json parser is adapted from How can I deserialize JSON to a simple Dictionary<string,string> in ASP.NET?
class Program
{
static void Main(string[] args)
{
string data = "{ 'test': 42, 'test2': 'test2\"', 'structure' : { 'field1': 'field1', 'field2': 44 } }";
dynamic x = new DynamicJsonObject(JsonMaker.ParseJSON(data));
Console.WriteLine(x.test2);
Console.WriteLine(x.structure.field1);
Console.ReadLine();
}
}
public class DynamicJsonObject : DynamicObject
{
private readonly IDictionary<string, object> _dictionary;
public DynamicJsonObject(IDictionary<string, object> dictionary)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public override string ToString()
{
var sb = new StringBuilder();
ToString(sb);
return sb.ToString();
}
private void ToString(StringBuilder sb)
{
sb.Append("{");
var firstInDictionary = true;
foreach (var pair in _dictionary)
{
if (!firstInDictionary)
sb.Append(",");
firstInDictionary = false;
var value = pair.Value;
var name = pair.Key;
if (value is string)
{
sb.AppendFormat("\"{0}\":\"{1}\"", name, value);
}
else if (value is IDictionary<string, object>)
{
sb.AppendFormat("\"{0}\":", name);
new DynamicJsonObject((IDictionary<string, object>)value).ToString(sb);
}
else if (value is ArrayList)
{
sb.Append("\"");
sb.Append(name);
sb.Append("\":[");
var firstInArray = true;
foreach (var arrayValue in (ArrayList)value)
{
if (!firstInArray)
sb.Append(",");
firstInArray = false;
if (arrayValue is IDictionary<string, object>)
new DynamicJsonObject((IDictionary<string, object>)arrayValue).ToString(sb);
else if (arrayValue is string)
sb.AppendFormat("\"{0}\"", arrayValue);
else
sb.AppendFormat("{0}", arrayValue);
}
sb.Append("]");
}
else
{
sb.AppendFormat("\"{0}\":{1}", name, value);
}
}
sb.Append("}");
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
if (!_dictionary.TryGetValue(binder.Name, out result))
{
// return null to avoid exception. caller can check for null this way...
result = null;
return true;
}
var dictionary = result as IDictionary<string, object>;
if (dictionary != null)
{
result = new DynamicJsonObject(dictionary);
return true;
}
var arrayList = result as ArrayList;
if (arrayList != null && arrayList.Count > 0)
{
if (arrayList[0] is IDictionary<string, object>)
result = new List<object>(arrayList.Cast<IDictionary<string, object>>().Select(x => new DynamicJsonObject(x)));
else
result = new List<object>(arrayList.Cast<object>());
}
return true;
}
}
public static class JsonMaker
{
public static Dictionary<string, object> ParseJSON(string json)
{
int end;
return ParseJSON(json, 0, out end);
}
private static Dictionary<string, object> ParseJSON(string json, int start, out int end)
{
Dictionary<string, object> dict = new Dictionary<string, object>();
bool escbegin = false;
bool escend = false;
bool inquotes = false;
string key = null;
int cend;
StringBuilder sb = new StringBuilder();
Dictionary<string, object> child = null;
List<object> arraylist = null;
Regex regex = new Regex(#"\\u([0-9a-z]{4})", RegexOptions.IgnoreCase);
int autoKey = 0;
bool inSingleQuotes = false;
bool inDoubleQuotes = false;
for (int i = start; i < json.Length; i++)
{
char c = json[i];
if (c == '\\') escbegin = !escbegin;
if (!escbegin)
{
if (c == '"' && !inSingleQuotes)
{
inDoubleQuotes = !inDoubleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
else if (c == '\'' && !inDoubleQuotes)
{
inSingleQuotes = !inSingleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
if (!inquotes)
{
switch (c)
{
case '{':
if (i != start)
{
child = ParseJSON(json, i, out cend);
if (arraylist != null) arraylist.Add(child);
else
{
dict.Add(key.Trim(), child);
key = null;
}
i = cend;
}
continue;
case '}':
end = i;
if (key != null)
{
if (arraylist != null) dict.Add(key.Trim(), arraylist);
else dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
}
return dict;
case '[':
arraylist = new List<object>();
continue;
case ']':
if (key == null)
{
key = "array" + autoKey.ToString();
autoKey++;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
dict.Add(key.Trim(), arraylist);
arraylist = null;
key = null;
continue;
case ',':
if (arraylist == null && key != null)
{
dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
key = null;
sb.Length = 0;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
continue;
case ':':
key = DecodeString(regex, sb.ToString());
sb.Length = 0;
continue;
}
}
}
sb.Append(c);
if (escend) escbegin = false;
if (escbegin) escend = true;
else escend = false;
}
end = json.Length - 1;
return dict; //theoretically shouldn't ever get here
}
private static string DecodeString(Regex regex, string str)
{
return Regex.Unescape(regex.Replace(str, match => char.ConvertFromUtf32(Int32.Parse(match.Groups[1].Value, System.Globalization.NumberStyles.HexNumber))));
}
}
Thanks again to Pete and the other guys for their brilliant post. I have wrapped mine around a SQL Server CLR scalar function which was incredibly useful in interrogating JSON stored in relational tables (I know some would say just use MongoDB!).
Please see below:
public class JsonHelper
{
/// <summary>
/// Parses the JSON.
/// Thanks to http://stackoverflow.com/questions/14967618/deserialize-json-to-class-manually-with-reflection
/// </summary>
/// <param name="json">The json.</param>
/// <returns></returns>
public static Dictionary<string, object> DeserializeJson(string json)
{
int end;
return DeserializeJson(json, 0, out end);
}
/// <summary>
/// Parses the JSON.
/// </summary>
/// <param name="json">The json.</param>
/// <param name="start">The start.</param>
/// <param name="end">The end.</param>
/// <returns></returns>
private static Dictionary<string, object> DeserializeJson(string json, int start, out int end)
{
var dict = new Dictionary<string, object>();
var escbegin = false;
var escend = false;
var inquotes = false;
string key = null;
var sb = new StringBuilder();
List<object> arraylist = null;
var regex = new Regex(#"\\u([0-9a-z]{4})", RegexOptions.IgnoreCase);
var autoKey = 0;
var inSingleQuotes = false;
var inDoubleQuotes = false;
for (var i = start; i < json.Length; i++)
{
var c = json[i];
if (c == '\\') escbegin = !escbegin;
if (!escbegin)
{
if (c == '"' && !inSingleQuotes)
{
inDoubleQuotes = !inDoubleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
if (c == '\'' && !inDoubleQuotes)
{
inSingleQuotes = !inSingleQuotes;
inquotes = !inquotes;
if (!inquotes && arraylist != null)
{
arraylist.Add(DecodeString(regex, sb.ToString()));
sb.Length = 0;
}
continue;
}
if (!inquotes)
{
switch (c)
{
case '{':
if (i != start)
{
int cend;
var child = DeserializeJson(json, i, out cend);
if (arraylist != null)
{
arraylist.Add(child);
}
else
{
dict.Add(key.Trim(), child);
key = null;
}
i = cend;
}
continue;
case '}':
end = i;
if (key != null)
{
if (arraylist != null) dict.Add(key.Trim(), arraylist);
else dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
}
return dict;
case '[':
arraylist = new List<object>();
continue;
case ']':
if (key == null)
{
key = "array" + autoKey;
autoKey++;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
dict.Add(key.Trim(), arraylist);
arraylist = null;
key = null;
continue;
case ',':
if (arraylist == null && key != null)
{
dict.Add(key.Trim(), DecodeString(regex, sb.ToString().Trim()));
key = null;
sb.Length = 0;
}
if (arraylist != null && sb.Length > 0)
{
arraylist.Add(sb.ToString());
sb.Length = 0;
}
continue;
case ':':
key = DecodeString(regex, sb.ToString());
sb.Length = 0;
continue;
}
}
}
sb.Append(c);
if (escend) escbegin = false;
escend = escbegin;
}
end = json.Length - 1;
return dict; // theoretically shouldn't ever get here
}
/// <summary>
/// Decodes the string.
/// </summary>
/// <param name="regex">The regex.</param>
/// <param name="str">The STR.</param>
/// <returns></returns>
private static string DecodeString(Regex regex, string str)
{
return
Regex.Unescape(regex.Replace(str,
match =>
char.ConvertFromUtf32(Int32.Parse(match.Groups[1].Value,
System.Globalization.NumberStyles
.HexNumber))));
}
/// <summary>
/// Returns true if string has an "appearance" of being JSON-like
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public static bool IsJson(string input)
{
input = input.Trim();
return input.StartsWith("{") && input.EndsWith("}")
|| input.StartsWith("[") && input.EndsWith("]");
}
}
The CLR function is below:
/// <summary>
/// Json "extractor" capable of extracting a value of a key using the object notation.
/// </summary>
/// <param name="json"></param>
/// <param name="key"></param>
/// <returns></returns>
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString fn_GetKeyValue(SqlString json, SqlString key)
{
var jsonString = json.ToString();
// Return if N/A
if (string.IsNullOrEmpty(jsonString) || !JsonHelper.IsJson(jsonString))
{
return json;
}
var keyString = key.ToString();
var jsonDictionary = JsonHelper.DeserializeJson(jsonString);
var lastNode = string.Empty;
foreach (var node in keyString.Split('.'))
{
if (!jsonDictionary.ContainsKey(node)) continue;
var childDictionary = jsonDictionary[node] as Dictionary<string, object>;
if (childDictionary != null)
{
jsonDictionary = childDictionary;
}
lastNode = node;
}
if (!jsonDictionary.ContainsKey(lastNode))
{
return null;
}
var keyValueString = jsonDictionary[lastNode].ToString();
return keyValueString == "null" ? null : new SqlString(keyValueString);
}
Usage would be:
-- Example 1 (querying a parent node)
SELECT dbo.fn_GetKeyValue('{
"ExchangeRates": {
"GBP": "1.2",
"USD": "2.0"
},
"Timestamp": "2015-04-10"
}', 'Timestamp');
-- Example 2 (querying a child node using a dot notation)
SELECT dbo.fn_GetKeyValue('{
"ExchangeRates": {
"GBP": "1.2",
"USD": "2.0"
},
"Timestamp": "2015-04-10"
}', 'ExchangeRates.GBP');