/// <summary> /// Get all GraphQL fields to query for given a Type. The Type needs to /// have JsonPath attributes specified for its properties for them to /// be included. /// </summary> /// <param name="inputType">Type of the class</param> /// <param name="convert">Convert the fields to GraphQL</param> /// <returns>List of (nested) GrapQL fields</returns> private static string[] GetQueryFields(Type inputType, bool convert = true) { // the type could be an array for which we need to know the child // types so we make a copy that we can overwrite. Type type = inputType.IsArray && inputType.HasElementType ? inputType.GetElementType() : inputType; // Each property that we're querying in the GraphQL response needs // to have a JsonPath attribute. This function will return the // properties that have an attribute. PropertyInfo[] properties = JsonPath.GetJsonPathProperties(type); // The JsonPath attribute is a JSONP string, so we need to translate // that into a GraphQL query list. List <string> fields = new List <string>(); // we need to cleanup some of the JSONPath specific things from the string // for which we use this regular expression. Regex regex = new Regex("[^a-zA-Z.0-9]+"); foreach (PropertyInfo property in properties) { JsonPath attribute = JsonPath.GetJsonPathAttribute(property); // this removes all characters that we don't want string cleanName = regex.Replace(attribute.Path, ""); // depending on the property type we need to handle this // differently. Type propertyType = property.PropertyType; if (propertyType.IsArray && propertyType.HasElementType) { propertyType = propertyType.GetElementType(); } // we need to do this recursively, otherwise we can just work // with primitives we don't recurse built-in classes. if (IsBasicType(propertyType)) { fields.Add(cleanName); continue; } string[] childFields = GetQueryFields(property.PropertyType, false); foreach (string childField in childFields) { fields.Add(string.Concat(cleanName, childField)); } } if (convert) { return(GraphQLFieldsFromJsonp(fields.ToArray())); } return(fields.ToArray()); }
/// <summary> /// Converts a JObject to a .NET object as per the provided type name /// </summary> /// <param name="objectData">JObject data to convert</param> /// <param name="type">Target type</param> /// <returns>Populated object</returns> public object ObjectFromJObject(JObject objectData, Type type) { if (objectData == null) { return(null); } // initialize empty result object object result = Activator.CreateInstance(type); // Each property that we're querying in the GraphQL response needs // to have a JsonPath attribute. We will only convert the properties // that have the JsonPath property set. PropertyInfo[] properties = JsonPath.GetJsonPathProperties(type); foreach (PropertyInfo property in properties) { try { // there should only be one of these attributes be specified JsonPath attribute = JsonPath.GetJsonPathAttribute(property); string jsonPath = attribute.Path; // depending on the type, we need to handle the conversion // differently. Type propertyType = property.PropertyType; if (!propertyType.IsArray) { JToken element = objectData.SelectToken(jsonPath, false); if (element == null) { continue; } // numbers, booleans and strings can easily be handled // by just moving over the value. if (propertyType.IsValueType || propertyType == typeof(string)) { property.SetValue(result, element.ToObject(property.PropertyType)); continue; } // objects (that we support) will be handled recursively JObject elementData = element.Value <JObject>(); property.SetValue(result, ObjectFromJObject(elementData, property.PropertyType)); continue; } // for an array, we need to know the child type and // instantiate every single element individually and we // need to add "[*]" to the JSON path to the array is // unfolded (if it is not properly set). if (jsonPath.IndexOf("[*]") == -1) { jsonPath = string.Concat(jsonPath, @"[*]"); } Type elementType = property.PropertyType.GetElementType(); List <object> tmp = new List <object>(); try { IEnumerable <JToken> elements = objectData.SelectTokens(jsonPath, false); if (elements == null) { property.SetValue(result, Array.CreateInstance(elementType, 0)); continue; } foreach (var childElement in elements) { // numbers, booleans and strings can easily // be handled by just moving over the value. if (elementType.IsValueType || elementType.Equals(typeof(string))) { tmp.Add(childElement.ToObject(elementType)); continue; } JObject childData = childElement.Value <JObject>(); tmp.Add(ObjectFromJObject(childData, elementType)); } } catch (Exception err) { Logger.WriteDebug(err.Message); } Array test = Array.CreateInstance(elementType, tmp.Count); for (int i = 0; i < test.Length; i++) { test.SetValue(tmp[i], i); } property.SetValue(result, test); } catch (Exception ex) { string errorMessage = string.Concat( "Error converting property '", property.Name, "' of type '", property.PropertyType.Name, "': ", ex.Message ); Logger.WriteWarning(errorMessage); } } return(result); }
private static string FormatGraphQLValue(object value, int depth = 0) { // some types require recursion as they can't be directly // converted. There is a maximum depth we're using to avoid stack // overflow. For null values we just return an empty string. if (value == null || depth > MAX_RECURSION_DEPTH) { return(@""); } // string is probably the most common one. // for strings, we need to add " and escape quotes. if (value is string) { string input = (string)value; string output = string.Concat("\"", input.Replace("\"", "\\\""), "\""); return(output); } // GUIDs are a special type of string if (value is Guid) { Guid input = (Guid)value; string output = string.Concat("\"", input.ToString(), "\""); return(output); } // boolean are expected to be lower case. if (value is bool?) { bool?input = (bool?)value; if (input.HasValue) { string output = input.ToString().ToLowerInvariant(); return(output); } return(""); } // boolean are expected to be lower case. if (value is bool) { bool input = (bool)value; string output = input.ToString().ToLowerInvariant(); return(output); } // DateTime values are expected to be in JSON format. if (value is DateTime) { DateTime input = (DateTime)value; string output = string.Concat("\"", input.ToString("yyyy-MM-ddTHH:MM:ss.sssZ"), "\""); return(output); } // numbers can be directly converted using their internal to string. if (value is ValueType) { if (value is byte || value is short || value is int || value is long || value is sbyte || value is ushort || value is uint || value is ulong || value is float || value is double || value is decimal) { string output = value.ToString(); return(output); } } // enumerations can be directly converted. This only works if the // enumeration in C# is named exactly as in the GraphQL API. if (value is Enum) { string output = value.ToString(); return(output); } // for arrays we don't care about the array's child types as they // will be handled by a recursive function if (value is Array) { IEnumerable input = value as IEnumerable; List <string> inputList = new List <string>(); foreach (object element in input) { string fValue = FormatGraphQLValue(element, depth + 1); // exclude empty values (strings that are of length 0. // note that if the parameter supplies an empty string, // it will actually be '\"\"' that is returned by // FormatGraphQLValue, so this still works if (string.IsNullOrEmpty(fValue)) { continue; } inputList.Add(fValue); } // create an output string. We don't need to handle the case // where inputList length is 0 as string.Join does that for us. string output = string.Concat(@"[", string.Join(@",", inputList), @"]"); return(output); } // for GraphQLParameters we also need to apply recursion. If the // rest of the code is adhering to a standard, this should only be // called once and the recursion is not needed. if (value is GraphQLParameters) { GraphQLParameters input = (GraphQLParameters)value; string output = string.Concat(@"{", input.ToString(), @"}"); return(output); } // here we format every remaining object type. We recursively // include every property that has the JsonPath attribute defined. PropertyInfo[] properties = JsonPath.GetJsonPathProperties(value); if (properties.Length > 0) { List <string> inputList = new List <string>(); foreach (PropertyInfo property in properties) { // this will return the first attribute matching. We don't // need to check for null as the properties list is already // filtered for only properties with the JsonPath attribute. JsonPath attr = JsonPath.GetJsonPathAttribute(property); // try formatting the value. If it is null, we won't add it // to the parameter list. string fValue = FormatGraphQLValue(property.GetValue(value), depth + 1); if (string.IsNullOrEmpty(fValue)) { continue; } string element = string.Concat(attr.BasePath, @":", fValue); inputList.Add(element); } // if there are no property values set, we consider this an // empty parameter set and we return an empty string. if (inputList.Count == 0) { return(@""); } string output = string.Concat(@"{", string.Join(@",", inputList), @"}"); return(output); } // Catch all. We should not reach this. throw new NebException("unsupported object type supplied"); }