예제 #1
0
        /// <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());
        }
예제 #2
0
        /// <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);
        }
예제 #3
0
        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");
        }