public ServicesOffering(ServiceCollection services, JObject config)
 {
     Config = config;
     Services = services;
 }
        ServiceCollection ParseConfigData(JObject joConfig)
        {
            // Create the ServiceCollection that will be returned:
            var coll = new ServiceCollection()
            {
                Errors = new List<string>(5),
                Services = new Dictionary<string, Service>(StringComparer.OrdinalIgnoreCase)
            };

            // Parse the root token dictionary first:
            var rootTokens = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            var jpTokens = joConfig.Property("$");
            if (jpTokens != null)
            {
                // Extract the key/value pairs onto a copy of the token dictionary:
                foreach (var prop in ((JObject)jpTokens.Value).Properties())
                {
                    // Newtonsoft.Json already guarantees that property names must be unique; it will throw a JsonReaderException in that case.
#if false
                    if (rootTokens.ContainsKey(prop.Name))
                    {
                        coll.Errors.Add("A token named '{0}' already exists in the '$' collection; cannot add a duplicate".F(prop.Name));
                        continue;
                    }
#endif
                    rootTokens[prop.Name] = getString(prop);
                }
            }

            // Parse root parameter types:
            var rootParameterTypes = new Dictionary<string, ParameterType>(StringComparer.OrdinalIgnoreCase);
            var jpParameterTypes = joConfig.Property("parameterTypes");
            if (jpParameterTypes != null)
            {
                var errors = new List<string>(5);
                parseParameterTypes(jpParameterTypes, errors, rootParameterTypes, (s) => s);
                if (errors.Count > 0)
                {
                    // Add errors encountered and keep going:
                    coll.Errors.AddRange(errors);
                }
            }

            // 'services' section is not optional:
            JToken jtServices;
            if (!joConfig.TryGetValue("services", out jtServices))
            {
                coll.Errors.Add("A 'services' section is required");
                return coll;
            }
            var joServices = (JObject)jtServices;

            // Parse each service descriptor:
            foreach (var jpService in joServices.Properties())
            {
                if (jpService.Name == "$") continue;
                var joService = (JObject)jpService.Value;

                // This property is a service:
                var svcErrors = new List<string>(5);

                Service baseService = null;
                IDictionary<string, string> tokens;
                string connectionString;
                IDictionary<string, ParameterType> parameterTypes;
                IDictionary<string, Method> methods;

                // Go through the properties of the named service object:
                var jpBase = joService.Property("base");
                if (jpBase != null)
                {
                    // NOTE(jsd): Forward references are not allowed. Base service must be defined before the current service in document order.
                    string baseName = getString(jpBase);
                    if (!coll.Services.TryGetValue(baseName, out baseService))
                    {
                        coll.Errors.Add("Unknown base service name '{0}' for service '{1}'; services must declared in document order".F(baseName, jpService.Name));
                        continue;
                    }

                    // Create copies of what's inherited from the base service to mutate:
                    connectionString = baseService.ConnectionString;
                    tokens = new Dictionary<string, string>(baseService.Tokens);
                    parameterTypes = new Dictionary<string, ParameterType>(baseService.ParameterTypes, StringComparer.OrdinalIgnoreCase);
                    methods = new Dictionary<string, Method>(baseService.Methods, StringComparer.OrdinalIgnoreCase);
                }
                else
                {
                    // Nothing inherited:
                    connectionString = null;
                    tokens = new Dictionary<string, string>(rootTokens, StringComparer.OrdinalIgnoreCase);
                    parameterTypes = new Dictionary<string, ParameterType>(rootParameterTypes, StringComparer.OrdinalIgnoreCase);
                    methods = new Dictionary<string, Method>(StringComparer.OrdinalIgnoreCase);
                }

                // Parse tokens:
                jpTokens = joService.Property("$");
                if (jpTokens != null)
                {
                    // Assigning a `null`?
                    if (jpTokens.Value.Type == JTokenType.Null)
                    {
                        // Clear out all inherited tokens:
                        tokens.Clear();
                    }
                    else
                    {
                        // Extract the key/value pairs onto our token dictionary:
                        foreach (var prop in ((JObject)jpTokens.Value).Properties())
                            // NOTE(jsd): No interpolation over tokens themselves.
                            tokens[prop.Name] = getString(prop);
                    }
                }

                // A lookup-or-null function used with `Interpolate`:
                Func<string, string> tokenLookup = (key) =>
                {
                    string value;
                    // TODO: add to a Warnings collection!
                    if (!tokens.TryGetValue(key, out value))
                        return null;
                    return value;
                };

                // Parse connection:
                var jpConnection = joService.Property("connection");
                if (jpConnection != null)
                {
                    connectionString = parseConnection(jpConnection, svcErrors, (s) => s.Interpolate(tokenLookup));
                }

                // Parse the parameter types:
                jpParameterTypes = joService.Property("parameterTypes");
                if (jpParameterTypes != null)
                {
                    // Assigning a `null`?
                    if (jpParameterTypes.Value.Type == JTokenType.Null)
                    {
                        // Clear out all inherited parameter types:
                        parameterTypes.Clear();
                    }
                    else
                    {
                        parseParameterTypes(jpParameterTypes, svcErrors, parameterTypes, (s) => s.Interpolate(tokenLookup));
                    }
                }

                // Create the service descriptor:
                Service svc = new Service()
                {
                    Name = jpService.Name,
                    BaseService = baseService,
                    ConnectionString = connectionString,
                    ParameterTypes = parameterTypes,
                    Methods = methods,
                    Tokens = tokens,
                    Errors = svcErrors
                };

                // Parse the methods:
                var jpMethods = joService.Property("methods");
                if (jpMethods != null)
                {
                    parseMethods(jpMethods, svc, connectionString, parameterTypes, methods, tokenLookup);
                }

                // Add the parsed service descriptor:
                coll.Services.Add(jpService.Name, svc);
            }

            // 'aliases' section is optional:
            var jpAliases = joConfig.Property("aliases");
            if (jpAliases != null)
            {
                // Parse the named aliases:
                var joAliases = (JObject)jpAliases.Value;
                foreach (var jpAlias in joAliases.Properties())
                {
                    // Add the existing Service reference to the new name:
                    string svcName = getString(jpAlias);

                    // Must find the existing service by its name first:
                    Service svcref;
                    if (!coll.Services.TryGetValue(svcName, out svcref))
                    {
                        coll.Errors.Add("Unknown service name '{0}' for alias '{1}'".F(svcName, jpAlias.Name));
                        continue;
                    }

                    // Can't add an alias name that already exists:
                    if (coll.Services.ContainsKey(jpAlias.Name))
                    {
                        coll.Errors.Add("Cannot add alias name '{0}' because that name is already in use".F(jpAlias.Name));
                        continue;
                    }

                    coll.Services.Add(jpAlias.Name, svcref);
                }
            }

            return coll;
        }