public TResult Bind <TResult>(Type objectType, JsonReader reader, IApplication application, Func <object, TResult> onParsed, Func <string, TResult> onDidNotBind, Func <string, TResult> onBindingFailure) { if (objectType.IsSubClassOfGeneric(typeof(IRef <>))) { return(GetGuid( (id) => { var refType = typeof(Ref <>).MakeGenericType(objectType.GenericTypeArguments); var value = Activator.CreateInstance(refType, id); return onParsed(value); }, onDidNotBind, onBindingFailure)); } if (objectType.IsSubClassOfGeneric(typeof(IRefOptional <>))) { return(GetGuidMaybe( id => { if (!id.HasValue) { var emptyValue = RefOptionalHelper.CreateEmpty(objectType.GenericTypeArguments.First()); return onParsed(emptyValue); } var refType = typeof(RefOptional <>).MakeGenericType(objectType.GenericTypeArguments); var value = Activator.CreateInstance(refType, id.Value); return onParsed(value); })); } if (objectType.IsSubClassOfGeneric(typeof(IRefs <>))) { return(GetGuids( ids => { var refType = typeof(Refs <>).MakeGenericType(objectType.GenericTypeArguments); var value = Activator.CreateInstance(refType, ids); return onParsed(value); })); } if (objectType.IsSubClassOfGeneric(typeof(IDictionary <,>))) { var dictionaryKeyType = objectType.GenericTypeArguments[0]; var dictionaryValueType = objectType.GenericTypeArguments[1]; var dictionaryType = typeof(Dictionary <,>).MakeGenericType(objectType.GenericTypeArguments); var instance = Activator.CreateInstance(dictionaryType); if (reader.TokenType != JsonToken.StartObject) { return(onParsed(instance)); } if (!reader.Read()) { return(onParsed(instance)); } var addMethod = dictionaryType.GetMethod("Add", BindingFlags.Public | BindingFlags.Instance); do { if (reader.TokenType == JsonToken.EndObject) { return(onParsed(instance)); } instance = StandardStringBindingsAttribute.BindDirect(dictionaryKeyType, reader.Path, keyValue => { var valueValue = Bind(dictionaryValueType, reader, application, v => v, why => dictionaryValueType.GetDefault(), why => dictionaryValueType.GetDefault()); addMethod.Invoke(instance, new object[] { keyValue, valueValue }); return(instance); }, (why) => instance, (why) => instance); } while (reader.Read()); } if (objectType == typeof(byte[])) { if (reader.TokenType == JsonToken.String) { var bytesString = reader.Value as string; var value = bytesString.FromBase64String(); return(onParsed(value)); } } if (objectType.IsAssignableFrom(typeof(Type))) { if (application is IApiApplication) { var apiApplication = application as IApiApplication; if (reader.TokenType == JsonToken.String) { var stringValue = reader.Value as string; var(success, type) = apiApplication.GetResourceType(stringValue, type => (true, type), () => (false, default(Type))); if (success) { return(onParsed(type)); } } } } if (objectType.IsNullable()) { var underlyingType = objectType.GetNullableUnderlyingType(); if (reader.TokenType == JsonToken.Null) { return(onParsed(objectType.GetDefault())); } return(Bind(underlyingType, reader, application, obj => onParsed(obj.AsNullable()), (why) => onParsed(objectType.GetDefault()), (why) => onParsed(objectType.GetDefault()))); } // As a last ditch effort, see if the JToken deserialization will work. var token = JToken.ReadFrom(reader); return(BindDirect(objectType, token, onParsed, onDidNotBind, onBindingFailure)); TR GetGuid <TR>( Func <Guid, TR> onGot, Func <string, TR> onIgnored, Func <string, TR> onFailed) { if (reader.TokenType == JsonToken.String) { var guidString = reader.Value as string; if (Guid.TryParse(guidString, out Guid guid)) { return(onGot(guid)); } return(onFailed($"Cannot parse `{guidString}` as a Guid.")); } if (reader.TokenType == JsonToken.StartObject) { if (!reader.Read()) { return(onIgnored("Empty object")); } return(GetGuid( guid => { while (reader.TokenType != JsonToken.EndObject) { if (!reader.Read()) { break; } } return onGot(guid); }, onIgnored, onFailed)); } if (reader.TokenType == JsonToken.PropertyName) { var propertyName = reader.Value as string; if (!reader.Read()) { return(onFailed("Property did not have value.")); } if (propertyName.ToLower() == "uuid") { return(GetGuid(onGot, onIgnored, onFailed)); } if (propertyName.ToLower() == "id") { return(GetGuid(onGot, onIgnored, onFailed)); } if (!reader.Read()) { return(onIgnored("'uuid' Property not found.")); } return(GetGuid(onGot, onIgnored, onFailed)); } return(onFailed($"Cannot decode token of type `{reader.TokenType}` to UUID.")); } TResult GetGuidMaybe(Func <Guid?, TResult> callback) { if (reader.TokenType == JsonToken.Null) { return(callback(default(Guid?))); } return(GetGuid( (x) => callback(x), onDidNotBind, onBindingFailure)); } TResult GetGuids(Func <Guid[], TResult> onGot) { if (reader.TokenType == JsonToken.Null) { return(onGot(new Guid[] { })); } if (reader.TokenType == JsonToken.StartArray) { var list = new List <Guid>(); while (reader.Read()) { if (reader.TokenType == JsonToken.EndArray) { break; } var result = GetGuid( g => new { why = string.Empty, g = g.AsOptional(), success = true, }, why => new { why = why, g = default(Guid?), success = true, }, why => new { why = why, g = default(Guid?), success = false, }); if (!result.success) { return(onBindingFailure(result.why)); } if (result.g.HasValue) { list.Add(result.g.Value); } } return(onGot(list.ToArray())); } return(onBindingFailure($"Cannot decode token of type `{reader.TokenType}` to {objectType.FullName}.")); } }
public static TResult BindDirect <TResult>(Type type, JToken content, Func <object, TResult> onParsed, Func <string, TResult> onDidNotBind, Func <string, TResult> onBindingFailure) { if (type.IsAssignableFrom(typeof(Guid))) { if (content.Type == JTokenType.Guid) { var guidValue = content.Value <Guid>(); return(onParsed(guidValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); if (Guid.TryParse(stringValue, out Guid guidValue)) { return(onParsed(guidValue)); } return(onBindingFailure($"Cannot convert `{stringValue}` to Guid.")); } var webId = ReadObject <WebId>(content); if (webId.IsDefaultOrNull()) { return(onBindingFailure("Null value for GUID.")); } var guidValueMaybe = webId.ToGuid(); if (!guidValueMaybe.HasValue) { return(onBindingFailure("Null WebId cannot be converted to a Guid.")); } var webIdGuidValue = guidValueMaybe.Value; return(onParsed(webIdGuidValue)); } if (type.IsSubClassOfGeneric(typeof(IRef <>))) { var activatableType = typeof(Ref <>).MakeGenericType(type.GenericTypeArguments); if (content.Type == JTokenType.Guid) { var guidValue = content.Value <Guid>(); var iref = Activator.CreateInstance(activatableType, guidValue); return(onParsed(iref)); } if (content.Type == JTokenType.String) { return(StandardStringBindingsAttribute.BindDirect(type, content.Value <string>(), onParsed, onDidNotBind, onBindingFailure)); } if (content.Type == JTokenType.Object) { var objectContent = (content as JObject); if (objectContent.TryGetValue("uuid", StringComparison.OrdinalIgnoreCase, out JToken idContentUuid)) { return(BindDirect(type, idContentUuid, onParsed, onDidNotBind, onBindingFailure)); } if (objectContent.TryGetValue("id", StringComparison.OrdinalIgnoreCase, out JToken idContentId)) { return(BindDirect(type, idContentId, onParsed, onDidNotBind, onBindingFailure)); } var guidStr = objectContent.ToString(); return(StandardStringBindingsAttribute.BindDirect(type, guidStr, onParsed, onDidNotBind, onBindingFailure)); } if (content.Type == JTokenType.Null) { return(onBindingFailure("Value was null.")); } } if (type.IsSubClassOfGeneric(typeof(IRefOptional <>))) { if (content.Type == JTokenType.Guid) { var guidValue = content.Value <Guid>(); var activatableType = typeof(IRefOptional <>).MakeGenericType(type.GenericTypeArguments); var iref = Activator.CreateInstance(activatableType, guidValue); return(onParsed(iref)); } if (content.Type == JTokenType.Object) { var objectContent = (content as JObject); if (objectContent.TryGetValue("uuid", StringComparison.OrdinalIgnoreCase, out JToken idContent)) { return(BindDirect(type, idContent, onParsed, onDidNotBind, onBindingFailure)); } var guidStr = objectContent.ToString(); return(StandardStringBindingsAttribute.BindDirect(type, guidStr, onParsed, onDidNotBind, onBindingFailure)); } if (content.Type == JTokenType.Null) { var irefOptional = RefOptionalHelper.CreateEmpty(type.GenericTypeArguments.First()); return(onParsed(irefOptional)); } // Standard string binding fallback at end of function will work for this case // if (content.Type == JTokenType.String) } if (type.IsSubClassOfGeneric(typeof(IRefs <>))) { var activatableType = typeof(Refs <>).MakeGenericType(type.GenericTypeArguments); if (content.Type == JTokenType.Guid) { var guidValue = content.Value <Guid>(); var guids = guidValue.AsArray(); var irefs = Activator.CreateInstance(activatableType, guids); return(onParsed(irefs)); } if (content.Type == JTokenType.String) { return(StandardStringBindingsAttribute.BindDirect(type, content.Value <string>(), onParsed, onDidNotBind, onBindingFailure)); } if (content.Type == JTokenType.Array) { var guids = ReadArray(content) .Select( token => BindDirect(typeof(Guid), token, guid => (Guid?)((Guid)guid), (why) => default(Guid?), (why) => default(Guid?))) .SelectWhereHasValue() .ToArray(); var irefs = Activator.CreateInstance(activatableType, guids); return(onParsed(irefs)); } } if (type == typeof(int)) { if (content.Type == JTokenType.Float) { var floatValue = content.Value <double>(); var intValue = (int)floatValue; return(onParsed(intValue)); } if (content.Type == JTokenType.Integer) { var intValue = content.Value <int>(); return(onParsed(intValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(StandardStringBindingsAttribute.BindDirect(type, stringValue, onParsed, onDidNotBind, onBindingFailure)); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(int).FullName}")); } if (type == typeof(double)) { if (content.Type == JTokenType.Float) { var floatValue = content.Value <double>(); return(onParsed(floatValue)); } if (content.Type == JTokenType.Integer) { var intValue = content.Value <int>(); var floatValue = (double)intValue; return(onParsed(floatValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(StandardStringBindingsAttribute.BindDirect(type, stringValue, onParsed, onDidNotBind, onBindingFailure)); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(double).FullName}")); } if (type == typeof(decimal)) { if (content.Type == JTokenType.Float) { var floatValue = content.Value <double>(); var decimalValule = (decimal)floatValue; return(onParsed(decimalValule)); } if (content.Type == JTokenType.Integer) { var intValue = content.Value <int>(); var floatValue = (decimal)intValue; return(onParsed(floatValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(StandardStringBindingsAttribute.BindDirect(type, stringValue, onParsed, onDidNotBind, onBindingFailure)); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(decimal).FullName}")); } if (type == typeof(DateTime)) { if (content.Type == JTokenType.Date) { var dateValue = content.Value <DateTime>(); return(onParsed(dateValue)); } if (content.Type == JTokenType.Integer) { var intValue = content.Value <long>(); var dateValue = new DateTime(intValue); return(onParsed(dateValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(StandardStringBindingsAttribute.BindDirect(type, stringValue, onParsed, onDidNotBind, onBindingFailure)); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(DateTime).FullName}")); } if (type == typeof(bool)) { if (content.Type == JTokenType.Boolean) { var boolValue = content.Value <bool>(); return(onParsed(boolValue)); } if (content.Type == JTokenType.Integer) { var intValue = content.Value <int>(); var boolValue = intValue != 0; return(onParsed(boolValue)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(StandardStringBindingsAttribute.BindDirect(type, stringValue, onParsed, onDidNotBind, onBindingFailure)); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(bool).FullName}")); } if (type == typeof(Func <Task <byte[]> >)) { if (content.Type == JTokenType.Bytes) { var bytes = content.Value <byte[]>(); Func <Task <byte[]> > callback = () => bytes.AsTask(); return(onParsed(callback)); } if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); if (stringValue.TryParseBase64String(out byte[] bytes)) { Func <Task <byte[]> > callback = () => bytes.AsTask(); return(onParsed(callback)); } if (Uri.TryCreate(stringValue, UriKind.Absolute, out Uri url)) { Func <Task <byte[]> > callback = async() => { using (var client = new HttpClient()) { using (var response = await client.GetAsync(url)) { var bytes = await response.Content.ReadAsByteArrayAsync(); return(bytes); } } }; return(onParsed(callback)); } return(GetHttpContentFromBase64(stringValue, context => { Func <Task <byte[]> > callback = context.ReadAsByteArrayAsync; return onParsed(callback); }, (prefix) => onBindingFailure($"Not a valid base64 string or a valid URL."))); } } if (type == typeof(HttpContent) || type.IsSubclassOf(typeof(HttpContent))) { if (content.Type == JTokenType.String) { var contentEncodedBase64String = content.Value <string>(); return(contentEncodedBase64String.MatchRegexInvoke( "data:(?<contentType>[^;]+);base64,(?<base64Data>.+)", (contentType, base64Data) => base64Data.PairWithValue(contentType), types => types.First( (ct, next) => { var base64EncodedData = ct.Key; var data = base64EncodedData.FromBase64String(); var contentType = ct.Value; var httpContent = new ByteArrayContent(data); httpContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); return onParsed(httpContent); }, () => onBindingFailure( $"Could not decode JSON Binary prefix:{contentEncodedBase64String.Substring(0, 25)}")))); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(HttpContent).FullName}")); } if (type == typeof(Func <Task <HttpContent> >)) { if (content.Type == JTokenType.String) { var stringValue = content.Value <string>(); return(GetHttpContentFromBase64(stringValue, (content) => { Func <Task <HttpContent> > func = () => content.AsTask(); return onParsed(content); }, (prefix) => { if (Uri.TryCreate(stringValue, UriKind.Absolute, out Uri url)) { Func <Task <HttpContent> > callback = async() => { using (var client = new HttpClient()) { using (var response = await client.GetAsync(url)) { // Resource disposal creates issues so a copy is needed var data = await response.Content.ReadAsByteArrayAsync(); var httpContent = new ByteArrayContent(data); foreach (var header in response.Content.Headers) { httpContent.Headers.Add(header.Key, header.Value); } return httpContent; } } }; return onParsed(callback); } return onBindingFailure($"Not a valid base64 string or a valid URL."); })); } return(onDidNotBind($"Cannot convert `{content.Type}` to {typeof(Func<Task<HttpContent>>).FullName}")); } TResult GetHttpContentFromBase64(string contentEncodedBase64String, Func <HttpContent, TResult> onSuccess, Func <string, TResult> onFailure) { return(contentEncodedBase64String.MatchRegexInvoke( "data:(?<contentType>[^;]+);base64,(?<base64Data>.+)", (contentType, base64Data) => base64Data.PairWithValue(contentType), types => types.First( (ct, next) => { var base64EncodedData = ct.Key; var data = base64EncodedData.FromBase64String(); var contentType = ct.Value; var httpContent = new ByteArrayContent(data); httpContent.Headers.ContentType = new MediaTypeHeaderValue(contentType); return onSuccess(httpContent); }, () => onFailure(contentEncodedBase64String.Substring(0, 25))))); } if (type.IsNullable()) { var nullableT = type.GetNullableUnderlyingType(); return(BindDirect(nullableT, content, (v) => { var nullableV = v.AsNullable(); return onParsed(nullableV); }, (why) => onParsed(type.GetDefault()), (why) => onParsed(type.GetDefault()))); } if (type.IsAssignableFrom(typeof(IDictionary <,>))) { var keyType = type.GenericTypeArguments[0]; var valueType = type.GenericTypeArguments[1]; var refType = typeof(Dictionary <,>).MakeGenericType(type.GenericTypeArguments); var refInstance = Activator.CreateInstance(refType); var addMethod = refType.GetMethod("Add"); //Dictionary<string, int> dict; //dict.Add() foreach (var kvpToken in ReadDictionary(content)) { var keyToken = kvpToken.Key; var valueToken = kvpToken.Value; string result = StandardStringBindingsAttribute.BindDirect(keyType, keyToken, keyValue => { return(BindDirect(valueType, valueToken, valueValue => { addMethod.Invoke(refInstance, new object[] { keyValue, valueValue }); return string.Empty; }, (why) => why, (why) => why)); }, (why) => why, (why) => why); } return(onParsed(refInstance)); } if (type == typeof(object)) { var objectValue = ReadObject(content); return(onParsed(objectValue)); } if (content is JObject) { var jObj = content as JObject; var jsonText = jObj.ToString(); var value = JsonConvert.DeserializeObject(jsonText, type); return(onParsed(value)); } if (content.Type == JTokenType.String) { return(StandardStringBindingsAttribute.BindDirect(type, content.Value <string>(), onParsed, onDidNotBind, onBindingFailure)); } //if (content.Type == JTokenType.Object || content.Type == JTokenType.Array) //{ // try // { // var value = Newtonsoft.Json.JsonConvert.DeserializeObject( // content.ToString(), type, bindConvert); // return onParsed(value); // } // catch (Newtonsoft.Json.JsonSerializationException) // { // throw; // } //} if (content.Type == JTokenType.Null) { // PropertyAttribute will not recognize null values as specified w/o this. if (type.IsAssignableFrom(typeof(string))) { return(onParsed((string)null)); } //var defaultValue = type.GetDefault(); //return onParsed(defaultValue); } return(onDidNotBind($"Could not find binding for type {type.FullName}")); }