private static IDictionary <string, object> MapResults(object queryObject, IEnumerable <ExecSelection <Info> > selections, GraphQLSchema <TContext> schema) { if (queryObject == null) // TODO: Check type non-null and throw exception { return(null); } var dict = new Dictionary <string, object>(); var type = queryObject.GetType(); var graphQlQueryType = schema.GetGQLTypeByQueryType(queryObject.GetType()); var queryTypeToSelections = CreateQueryTypeToSelectionsMapping(graphQlQueryType, selections.ToList()); if (!queryTypeToSelections.ContainsKey(type)) { return(dict); } foreach (var map in selections) { var key = map.Name; var field = map.SchemaField.Field(); var typeConditionType = map.TypeCondition?.Value?.Type(); var propertyName = field.Name == "__typename" ? field.Name : CreatePropertyName(graphQlQueryType, typeConditionType, field.Name); object obj = null; if (field.IsPost) { obj = field.PostFieldFunc(); } else if (type.GetProperty(propertyName) != null) { obj = type.GetProperty(propertyName).GetGetMethod().Invoke(queryObject, new object[] {}); } else { continue; } // Filter fields for selections with type conditions - the '__typename'-property has to be present. if (map.TypeCondition != null) { // The selection has a type condition, i.e. the result has a type with included types. // The result contains all fields of all included types and has to be filtered. // Sample: // // Types: // - Character [name: string] // - Human [height: float] extends Character // - Stormtrooper [specialization: string] extends Human // - Droid [orimaryFunction: string] extends Character // // Sample query: // query { heros { name, ... on Human { height }, ... on Stormtrooper { specialization }, ... on Droid { primaryFunction } } } // // The ExecSelection for 'height', 'specialization' and 'primaryFunction' have a type condition with the following type names: // - height: Human // - specialization: Stormtrooper // - primaryFunction: Droid // // To filter the result properly, we have to consider the following cases: // - Human: // - Include: 'name', 'height' // - Exclude: 'specialization', 'primaryFunction' // => (1) Filter results of selections with a type-condition-name != result.__typename // // - Stormtrooper // - Include: 'name', 'height', and 'specialization' // - Exclude: 'primaryFunction' // => Same as Human (1), but: // => (2) include results of selections with the same type-condition-name of any ancestor-type. // var selectionConditionTypeName = map.TypeCondition.Value?.TypeName; var typenameProp = type.GetRuntimeProperty("__typename"); var resultTypeName = (string)typenameProp?.GetValue(queryObject); // (1) type-condition-name != result.__typename if (selectionConditionTypeName != resultTypeName) { // (2) Check ancestor types var resultGraphQlType = schema.Types.FirstOrDefault(t => t.Name == resultTypeName); if (resultGraphQlType != null && resultGraphQlType.Name != selectionConditionTypeName && resultGraphQlType.Interfaces.All(i => i.Name != selectionConditionTypeName)) { continue; } } } if (obj == null && !dict.ContainsKey(key)) { dict.Add(key, null); continue; } if (field.IsPost && map.Selections.Any()) { var selector = GetSelector(schema, field.Type, map.Selections.Values(), new ExpressionOptions(null, castAssignment: true, nullCheckLists: true, typeCheckInheritance: true)); obj = selector.Compile().DynamicInvoke(obj); } // Due to type conditions a key might occur multiple times if (map.Selections.Any()) { var listObj = obj as IEnumerable <object>; if (listObj != null && !dict.ContainsKey(key)) { dict.Add(key, listObj.Select(o => MapResults(o, map.Selections.Values(), schema)).ToList()); } else if (obj != null && !dict.ContainsKey(key)) { dict.Add(key, MapResults(obj, map.Selections.Values(), schema)); } else { throw new Exception("Shouldn't be here"); } } else { if (!dict.ContainsKey(key)) { dict.Add(key, obj); } } } return(dict); }