ピートと、私を正しい軌道に乗せてくれた他の人たちに感謝します。私の場合、JSON 文字列を SQL CLR 関数の厳密に型指定されたオブジェクトに逆シリアル化する必要もあったため、「安全に」使用できるライブラリが制限されていました (詳細はこちら)。
ParseJSON のコードを変更しました。これは、配列の配列を逆シリアル化Dictionary<string, object>
できるように逆シリアル化しますが、これはできませんでした。また、JavaScriptConverter
またはSystem.Runtime.Serialization
ライブラリを使用せずに、結果の辞書を厳密に型指定されたオブジェクトにキャストする方法をいくつか開発しました。このコードでは、次のことができます。
//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);
}
ParseJSON メソッドは、JSON 文字列を入力として受け取り、オプションでDictionary<string, string>
それを Type にキャストしようとしT
ます。辞書は、JSON 文字列の任意のプロパティをオブジェクトのプロパティにマップするために使用されます (たとえば、"jdata"/" data" ディクショナリは上記で宣言されています)。
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;
}
以下は、JSON 解析用に変更した方法です (配列の配列を解析できます)。
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))));
}
次のメソッドは、前のメソッドから返された辞書を強い型指定されたオブジェクトにキャストしようとします。長いことはわかっていますが、うまく機能します。
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;
}
}
これが、これを行う方法をまだ探している人に役立つことを願っています。