private static IComponentFactory GetComponentJson(IExceptionContext ectx, Type signatureType, string name, JObject settings, ModuleCatalog catalog)
        {
            Contracts.AssertValue(ectx);
            ectx.AssertValue(signatureType);
            ectx.AssertNonEmpty(name);
            ectx.AssertValueOrNull(settings);
            ectx.AssertValue(catalog);

            if (!catalog.TryGetComponentKind(signatureType, out string kind))
            {
                throw ectx.Except($"Component type '{signatureType}' is not a valid signature type.");
            }

            if (!catalog.TryFindComponent(kind, name, out ModuleCatalog.ComponentInfo component))
            {
                var available = catalog.GetAllComponents(kind).Select(x => $"'{x.Name}'");
                throw ectx.Except($"Component '{name}' of kind '{kind}' is not found. Available components are: {string.Join(", ", available)}");
            }

            var inputBuilder = new InputBuilder(ectx, component.ArgumentType, catalog);

            if (settings != null)
            {
                foreach (var pair in settings)
                {
                    if (!inputBuilder.TrySetValueJson(pair.Key, pair.Value))
                    {
                        throw ectx.Except($"Unexpected value for component '{name}', field '{pair.Key}': '{pair.Value}'");
                    }
                }
            }

            var missing = inputBuilder.GetMissingValues().ToArray();

            if (missing.Length > 0)
            {
                throw ectx.Except($"The following required inputs were not provided for component '{name}': {string.Join(", ", missing)}");
            }
            return(inputBuilder.GetInstance() as IComponentFactory);
        }
        private static object ParseJsonValue(IExceptionContext ectx, Type type, Attributes attributes, JToken value, ModuleCatalog catalog)
        {
            Contracts.AssertValue(ectx);
            ectx.AssertValue(type);
            ectx.AssertValueOrNull(value);
            ectx.AssertValue(catalog);

            if (value == null)
            {
                return(null);
            }

            if (value is JValue val && val.Value == null)
            {
                return(null);
            }

            if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Optional <>) || type.GetGenericTypeDefinition() == typeof(Nullable <>)))
            {
                type = type.GetGenericArguments()[0];
            }

            if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Var <>)))
            {
                string varName = value.Value <string>();
                ectx.Check(VariableBinding.IsBindingToken(value), "Variable name expected.");
                var variable   = Activator.CreateInstance(type) as IVarSerializationHelper;
                var varBinding = VariableBinding.Create(ectx, varName);
                variable.VarName = varBinding.VariableName;
                return(variable);
            }

            if (type == typeof(JArray) && value is JArray)
            {
                return(value);
            }

            TlcModule.DataKind dt = TlcModule.GetDataType(type);

            try
            {
                switch (dt)
                {
                case TlcModule.DataKind.Bool:
                    return(value.Value <bool>());

                case TlcModule.DataKind.String:
                    return(value.Value <string>());

                case TlcModule.DataKind.Char:
                    return(value.Value <char>());

                case TlcModule.DataKind.Enum:
                    if (!Enum.IsDefined(type, value.Value <string>()))
                    {
                        throw ectx.Except($"Requested value '{value.Value<string>()}' is not a member of the Enum type '{type.Name}'");
                    }
                    return(Enum.Parse(type, value.Value <string>()));

                case TlcModule.DataKind.Float:
                    if (type == typeof(double))
                    {
                        return(value.Value <double>());
                    }
                    else if (type == typeof(float))
                    {
                        return(value.Value <float>());
                    }
                    else
                    {
                        ectx.Assert(false);
                        throw ectx.ExceptNotSupp();
                    }

                case TlcModule.DataKind.Array:
                    var ja = value as JArray;
                    ectx.Check(ja != null, "Expected array value");
                    Func <IExceptionContext, JArray, Attributes, ModuleCatalog, object> makeArray = MakeArray <int>;
                    return(Utils.MarshalInvoke(makeArray, type.GetElementType(), ectx, ja, attributes, catalog));

                case TlcModule.DataKind.Int:
                    if (type == typeof(long))
                    {
                        return(value.Value <long>());
                    }
                    if (type == typeof(int))
                    {
                        return(value.Value <int>());
                    }
                    ectx.Assert(false);
                    throw ectx.ExceptNotSupp();

                case TlcModule.DataKind.UInt:
                    if (type == typeof(ulong))
                    {
                        return(value.Value <ulong>());
                    }
                    if (type == typeof(uint))
                    {
                        return(value.Value <uint>());
                    }
                    ectx.Assert(false);
                    throw ectx.ExceptNotSupp();

                case TlcModule.DataKind.Dictionary:
                    ectx.Check(value is JObject, "Expected object value");
                    Func <IExceptionContext, JObject, Attributes, ModuleCatalog, object> makeDict = MakeDictionary <int>;
                    return(Utils.MarshalInvoke(makeDict, type.GetGenericArguments()[1], ectx, (JObject)value, attributes, catalog));

                case TlcModule.DataKind.Component:
                    var jo = value as JObject;
                    ectx.Check(jo != null, "Expected object value");
                    // REVIEW: consider accepting strings alone.
                    var jName = jo[FieldNames.Name];
                    ectx.Check(jName != null, "Field '" + FieldNames.Name + "' is required for component.");
                    ectx.Check(jName is JValue, "Expected '" + FieldNames.Name + "' field to be a string.");
                    var name = jName.Value <string>();
                    ectx.Check(jo[FieldNames.Settings] == null || jo[FieldNames.Settings] is JObject,
                               "Expected '" + FieldNames.Settings + "' field to be an object");
                    return(GetComponentJson(ectx, type, name, jo[FieldNames.Settings] as JObject, catalog));

                default:
                    var settings = value as JObject;
                    ectx.Check(settings != null, "Expected object value");
                    var inputBuilder = new InputBuilder(ectx, type, catalog);

                    if (inputBuilder._fields.Length == 0)
                    {
                        throw ectx.Except($"Unsupported input type: {dt}");
                    }

                    if (settings != null)
                    {
                        foreach (var pair in settings)
                        {
                            if (!inputBuilder.TrySetValueJson(pair.Key, pair.Value))
                            {
                                throw ectx.Except($"Unexpected value for component '{type}', field '{pair.Key}': '{pair.Value}'");
                            }
                        }
                    }

                    var missing = inputBuilder.GetMissingValues().ToArray();
                    if (missing.Length > 0)
                    {
                        throw ectx.Except($"The following required inputs were not provided for component '{type}': {string.Join(", ", missing)}");
                    }
                    return(inputBuilder.GetInstance());
                }
            }
            catch (FormatException ex)
            {
                if (ex.IsMarked())
                {
                    throw;
                }
                throw ectx.Except(ex, $"Failed to parse JSON value '{value}' as {type}");
            }
        }
        public JObject GetJsonObject(object instance, Dictionary <string, List <ParameterBinding> > inputBindingMap, Dictionary <ParameterBinding, VariableBinding> inputMap)
        {
            Contracts.CheckValue(instance, nameof(instance));
            Contracts.Check(instance.GetType() == _type);

            var result   = new JObject();
            var defaults = Activator.CreateInstance(_type);

            for (int i = 0; i < _fields.Length; i++)
            {
                var field       = _fields[i];
                var attr        = _attrs[i];
                var instanceVal = field.GetValue(instance);
                var defaultsVal = field.GetValue(defaults);

                if (inputBindingMap.TryGetValue(field.Name, out List <ParameterBinding> bindings))
                {
                    // Handle variables.
                    Contracts.Assert(bindings.Count > 0);
                    VariableBinding varBinding;
                    var             paramBinding = bindings[0];
                    if (paramBinding is SimpleParameterBinding)
                    {
                        Contracts.Assert(bindings.Count == 1);
                        bool success = inputMap.TryGetValue(paramBinding, out varBinding);
                        Contracts.Assert(success);
                        Contracts.AssertValue(varBinding);

                        result.Add(field.Name, new JValue(varBinding.ToJson()));
                    }
                    else if (paramBinding is ArrayIndexParameterBinding)
                    {
                        // Array parameter bindings.
                        var array = new JArray();
                        foreach (var parameterBinding in bindings)
                        {
                            Contracts.Assert(parameterBinding is ArrayIndexParameterBinding);
                            bool success = inputMap.TryGetValue(parameterBinding, out varBinding);
                            Contracts.Assert(success);
                            Contracts.AssertValue(varBinding);
                            array.Add(new JValue(varBinding.ToJson()));
                        }

                        result.Add(field.Name, array);
                    }
                    else
                    {
                        // Dictionary parameter bindings. Not supported yet.
                        Contracts.Assert(paramBinding is DictionaryKeyParameterBinding);
                        throw Contracts.ExceptNotImpl("Dictionary of variables not yet implemented.");
                    }
                }
                else if (instanceVal == null && defaultsVal != null)
                {
                    // Handle null values.
                    result.Add(field.Name, new JValue(instanceVal));
                }
                else if (instanceVal != null && (attr.Input.IsRequired || !instanceVal.Equals(defaultsVal)))
                {
                    // A required field will be serialized regardless of whether or not its value is identical to the default.
                    var type = instanceVal.GetType();
                    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Optional <>))
                    {
                        var isExplicit = ExtractOptional(ref instanceVal, ref type);
                        if (!isExplicit)
                        {
                            continue;
                        }
                    }

                    if (type == typeof(JArray))
                    {
                        result.Add(field.Name, (JArray)instanceVal);
                    }
                    else if (type.IsGenericType &&
                             ((type.GetGenericTypeDefinition() == typeof(Var <>)) ||
                              type.GetGenericTypeDefinition() == typeof(ArrayVar <>) ||
                              type.GetGenericTypeDefinition() == typeof(DictionaryVar <>)))
                    {
                        result.Add(field.Name, new JValue($"${((IVarSerializationHelper)instanceVal).VarName}"));
                    }
                    else if (type == typeof(bool) ||
                             type == typeof(string) ||
                             type == typeof(char) ||
                             type == typeof(double) ||
                             type == typeof(float) ||
                             type == typeof(int) ||
                             type == typeof(long) ||
                             type == typeof(uint) ||
                             type == typeof(ulong))
                    {
                        // Handle simple types.
                        result.Add(field.Name, new JValue(instanceVal));
                    }
                    else if (type.IsEnum)
                    {
                        // Handle enums.
                        result.Add(field.Name, new JValue(instanceVal.ToString()));
                    }
                    else if (type.IsArray)
                    {
                        // Handle arrays.
                        var array       = (Array)instanceVal;
                        var jarray      = new JArray();
                        var elementType = type.GetElementType();
                        if (elementType == typeof(bool) ||
                            elementType == typeof(string) ||
                            elementType == typeof(char) ||
                            elementType == typeof(double) ||
                            elementType == typeof(float) ||
                            elementType == typeof(int) ||
                            elementType == typeof(long) ||
                            elementType == typeof(uint) ||
                            elementType == typeof(ulong))
                        {
                            foreach (object item in array)
                            {
                                jarray.Add(new JValue(item));
                            }
                        }
                        else
                        {
                            var builder = new InputBuilder(_ectx, elementType, _catalog);
                            foreach (object item in array)
                            {
                                jarray.Add(builder.GetJsonObject(item, inputBindingMap, inputMap));
                            }
                        }
                        result.Add(field.Name, jarray);
                    }
                    else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary <,>) &&
                             type.GetGenericArguments()[0] == typeof(string))
                    {
                        // Handle dictionaries.
                        // REVIEW: Needs to be implemented when we will have entry point arguments that contain dictionaries.
                    }
                    else if (typeof(IComponentFactory).IsAssignableFrom(type))
                    {
                        // Handle component factories.
                        bool success = _catalog.TryFindComponent(type, out ModuleCatalog.ComponentInfo instanceInfo);
                        Contracts.Assert(success);
                        var builder      = new InputBuilder(_ectx, type, _catalog);
                        var instSettings = builder.GetJsonObject(instanceVal, inputBindingMap, inputMap);

                        ModuleCatalog.ComponentInfo defaultInfo = null;
                        JObject defSettings = new JObject();
                        if (defaultsVal != null)
                        {
                            var deftype = defaultsVal.GetType();
                            if (deftype.IsGenericType && deftype.GetGenericTypeDefinition() == typeof(Optional <>))
                            {
                                ExtractOptional(ref defaultsVal, ref deftype);
                            }
                            success = _catalog.TryFindComponent(deftype, out defaultInfo);
                            Contracts.Assert(success);
                            builder     = new InputBuilder(_ectx, deftype, _catalog);
                            defSettings = builder.GetJsonObject(defaultsVal, inputBindingMap, inputMap);
                        }

                        if (instanceInfo.Name != defaultInfo?.Name || instSettings.ToString() != defSettings.ToString())
                        {
                            var jcomponent = new JObject
                            {
                                { FieldNames.Name, new JValue(instanceInfo.Name) }
                            };
                            if (instSettings.ToString() != defSettings.ToString())
                            {
                                jcomponent.Add(FieldNames.Settings, instSettings);
                            }
                            result.Add(field.Name, jcomponent);
                        }
                    }
                    else
                    {
                        // REVIEW: pass in the bindings once we support variables in inner fields.

                        // Handle structs.
                        var builder = new InputBuilder(_ectx, type, _catalog);
                        result.Add(field.Name, builder.GetJsonObject(instanceVal, new Dictionary <string, List <ParameterBinding> >(),
                                                                     new Dictionary <ParameterBinding, VariableBinding>()));
                    }
                }
            }

            return(result);
        }