Created
January 24, 2012 05:56
-
-
Save Romiko/1668273 to your computer and use it in GitHub Desktop.
Neo4jClient JSON Deserializer
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
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Reflection; | |
using System.Text.RegularExpressions; | |
using Newtonsoft.Json; | |
using Newtonsoft.Json.Linq; | |
using RestSharp; | |
using RestSharp.Deserializers; | |
using RestSharp.Extensions; | |
using System.Globalization; | |
namespace Neo4jClient.Deserializer | |
{ | |
public class CustomJsonDeserializer : IDeserializer | |
{ | |
static readonly Regex DateRegex = new Regex(@"/Date\([-]?\d+([+-]\d+)?\)/"); | |
static readonly Regex DateTypeNameRegex = new Regex(@"(?<=(?<quote>['""])/)Date(?=\(.*?\)/\k<quote>)"); | |
public string RootElement { get; set; } | |
public string Namespace { get; set; } | |
public string DateFormat { get; set; } | |
public CultureInfo Culture { get; set; } | |
public CustomJsonDeserializer() | |
{ | |
Culture = CultureInfo.InvariantCulture; | |
} | |
public T Deserialize<T>(string content) where T : new() | |
{ | |
return Deserialize<T>(new RestResponse | |
{ | |
Content = content | |
}); | |
} | |
public T Deserialize<T>(RestResponse response) where T : new() | |
{ | |
var target = new T(); | |
// Replace all /Date(1234+0200)/ instances with /NeoDate(1234+0200)/ | |
response.Content = DateTypeNameRegex.Replace(response.Content, "NeoDate"); | |
if (target is IList) | |
{ | |
var objType = target.GetType(); | |
if (RootElement.HasValue()) | |
{ | |
var root = FindRoot(response.Content); | |
target = (T)BuildList(objType, root.Children()); | |
} | |
else | |
{ | |
var json = JArray.Parse(response.Content); | |
target = (T)BuildList(objType, json.Root.Children()); | |
} | |
} | |
else if (target is IDictionary) | |
{ | |
var root = FindRoot(response.Content); | |
target = (T)BuildDictionary(target.GetType(), root.Children()); | |
} | |
else | |
{ | |
var root = FindRoot(response.Content); | |
Map(target, root); | |
} | |
return target; | |
} | |
private JToken FindRoot(string content) | |
{ | |
var json = JObject.Parse(content); | |
var root = json.Root; | |
if (RootElement.HasValue()) | |
root = json.SelectToken(RootElement); | |
return root; | |
} | |
private void Map(object x, JToken json) | |
{ | |
var objType = x.GetType(); | |
var props = GetPropertiesForType(objType); | |
foreach (var propertyName in props.Keys) | |
{ | |
var prop = props[propertyName]; | |
var type = prop.PropertyType; | |
var name = propertyName; | |
var value = json[name]; | |
var actualName = name; | |
if (value == null || value.Type == JTokenType.Null) | |
{ | |
continue; | |
} | |
// check for nullable and extract underlying type | |
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) | |
{ | |
type = type.GetGenericArguments()[0]; | |
} | |
if (type.IsPrimitive) | |
{ | |
// no primitives can contain quotes so we can safely remove them | |
// allows converting a json value like {"index": "1"} to an int | |
var tmpVal = value.AsString().Replace("\"", string.Empty); | |
prop.SetValue(x, tmpVal.ChangeType(type), null); | |
} | |
else if (type.IsEnum) | |
{ | |
var raw = value.AsString(); | |
var converted = Enum.Parse(type, raw, false); | |
prop.SetValue(x, converted, null); | |
} | |
else if (type == typeof(Uri)) | |
{ | |
var raw = value.AsString(); | |
var uri = new Uri(raw, UriKind.RelativeOrAbsolute); | |
prop.SetValue(x, uri, null); | |
} | |
else if (type == typeof(string)) | |
{ | |
var raw = value.AsString(); | |
prop.SetValue(x, raw, null); | |
} | |
else if (type == typeof(DateTime)) | |
{ | |
throw new NotSupportedException("DateTime values are not supported. Use DateTimeOffset instead."); | |
} | |
else if (type == typeof(DateTimeOffset)) | |
{ | |
var dateTimeOffset = ParseDateTimeOffset(value); | |
if (dateTimeOffset.HasValue) | |
prop.SetValue(x, dateTimeOffset.Value, null); | |
} | |
else if (type == typeof(Decimal)) | |
{ | |
var dec = Decimal.Parse(value.AsString(), Culture); | |
prop.SetValue(x, dec, null); | |
} | |
else if (type == typeof(Guid)) | |
{ | |
var raw = value.AsString(); | |
var guid = string.IsNullOrEmpty(raw) ? Guid.Empty : new Guid(raw); | |
prop.SetValue(x, guid, null); | |
} | |
else if (type.IsGenericType) | |
{ | |
var genericTypeDef = type.GetGenericTypeDefinition(); | |
if (genericTypeDef == typeof(List<>)) | |
{ | |
var list = BuildList(type, value.Children()); | |
prop.SetValue(x, list, null); | |
} | |
else if (genericTypeDef == typeof(Dictionary<,>)) | |
{ | |
var keyType = type.GetGenericArguments()[0]; | |
// only supports Dict<string, T>() | |
if (keyType == typeof(string)) | |
{ | |
var dict = BuildDictionary(type, value.Children()); | |
prop.SetValue(x, dict, null); | |
} | |
} | |
else | |
{ | |
// nested property classes | |
var item = CreateAndMap(type, json[actualName]); | |
prop.SetValue(x, item, null); | |
} | |
} | |
else | |
{ | |
// nested property classes | |
var item = CreateAndMap(type, json[actualName]); | |
prop.SetValue(x, item, null); | |
} | |
} | |
} | |
static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> PropertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>(); | |
static readonly object PropertyInfoCacheLock = new object(); | |
static Dictionary<string, PropertyInfo> GetPropertiesForType(Type objType) | |
{ | |
Dictionary<string, PropertyInfo> result; | |
if (PropertyInfoCache.TryGetValue(objType, out result)) | |
return result; | |
lock (PropertyInfoCacheLock) | |
{ | |
if (PropertyInfoCache.TryGetValue(objType, out result)) | |
return result; | |
var properties = objType | |
.GetProperties() | |
.Where(p => p.CanWrite) | |
.Select(p => | |
{ | |
var attributes = | |
(JsonPropertyAttribute[]) p.GetCustomAttributes(typeof (JsonPropertyAttribute), true); | |
return new | |
{ | |
Name = attributes.Any() ? attributes.Single().PropertyName : p.Name, | |
Property = p | |
}; | |
}); | |
return properties.ToDictionary(p => p.Name, p => p.Property); | |
} | |
} | |
static DateTimeOffset? ParseDateTimeOffset(JToken value) | |
{ | |
var rawValue = value.AsString(); | |
if (string.IsNullOrWhiteSpace(rawValue)) | |
return null; | |
rawValue = rawValue.Replace("NeoDate", "Date"); | |
if (!DateRegex.IsMatch(rawValue)) | |
return null; | |
var text = string.Format("{{\"a\":\"{0}\"}}", rawValue); | |
var reader = new JsonTextReader(new StringReader(text)); | |
reader.Read(); // JsonToken.StartObject | |
reader.Read(); // JsonToken.PropertyName | |
return reader.ReadAsDateTimeOffset(); | |
} | |
private object CreateAndMap(Type type, JToken element) | |
{ | |
object instance; | |
if (type.IsGenericType) | |
{ | |
var genericTypeDef = type.GetGenericTypeDefinition(); | |
if (genericTypeDef == typeof(Dictionary<,>)) | |
{ | |
instance = BuildDictionary(type, element.Children()); | |
} | |
else if (genericTypeDef == typeof(List<>)) | |
{ | |
instance = BuildList(type, element.Children()); | |
} | |
else if (type == typeof(string)) | |
{ | |
instance = (string)element; | |
} | |
else | |
{ | |
instance = Activator.CreateInstance(type); | |
Map(instance, element); | |
} | |
} | |
else if (type == typeof(string)) | |
{ | |
instance = (string)element; | |
} | |
else | |
{ | |
instance = Activator.CreateInstance(type); | |
Map(instance, element); | |
} | |
return instance; | |
} | |
private IDictionary BuildDictionary(Type type, JEnumerable<JToken> elements) | |
{ | |
var dict = (IDictionary)Activator.CreateInstance(type); | |
var valueType = type.GetGenericArguments()[1]; | |
foreach (JProperty child in elements) | |
{ | |
var key = child.Name; | |
var item = CreateAndMap(valueType, child.Value); | |
dict.Add(key, item); | |
} | |
return dict; | |
} | |
private IList BuildList(Type type, JEnumerable<JToken> elements) | |
{ | |
var list = (IList)Activator.CreateInstance(type); | |
var itemType = type | |
.GetInterfaces() | |
.Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IList<>)) | |
.Select(i => i.GetGenericArguments().First()) | |
.Single(); | |
foreach (var element in elements) | |
{ | |
if (itemType.IsPrimitive) | |
{ | |
var value = element as JValue; | |
if (value != null) | |
{ | |
list.Add(value.Value.ChangeType(itemType)); | |
} | |
} | |
else if (itemType == typeof(string)) | |
{ | |
list.Add(element.AsString()); | |
} | |
else | |
{ | |
var item = CreateAndMap(itemType, element); | |
list.Add(item); | |
} | |
} | |
return list; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment