private static void WriteValue(object obj, JsonWriter writer,
                                       bool writer_is_private,
                                       int depth)
        {
            if (depth > max_nesting_depth)
            {
                throw new JsonException(
                          String.Format("Max allowed object depth reached while " +
                                        "trying to export from type {0}",
                                        obj.GetType()));
            }

            if (obj == null)
            {
                writer.Write(null);
                return;
            }

            if (obj is IJsonWrapper)
            {
                if (writer_is_private)
                {
                    writer.TextWriter.Write(((IJsonWrapper)obj).ToJson());
                }
                else
                {
                    ((IJsonWrapper)obj).ToJson(writer);
                }

                return;
            }

            if (obj is String)
            {
                writer.Write((string)obj);
                return;
            }

            if (obj is Double)
            {
                writer.Write((double)obj);
                return;
            }

            if (obj is Int32)
            {
                writer.Write((int)obj);
                return;
            }

            if (obj is Boolean)
            {
                writer.Write((bool)obj);
                return;
            }

            if (obj is Int64)
            {
                writer.Write((long)obj);
                return;
            }

            if (obj is Array)
            {
                writer.WriteArrayStart();

                foreach (object elem in (Array)obj)
                {
                    WriteValue(elem, writer, writer_is_private, depth + 1);
                }

                writer.WriteArrayEnd();

                return;
            }

            if (obj is IList)
            {
                writer.WriteArrayStart();
                foreach (object elem in (IList)obj)
                {
                    WriteValue(elem, writer, writer_is_private, depth + 1);
                }
                writer.WriteArrayEnd();

                return;
            }

            if (obj is IDictionary)
            {
                writer.WriteObjectStart();
                foreach (DictionaryEntry entry in (IDictionary)obj)
                {
                    writer.WritePropertyName((string)entry.Key);
                    WriteValue(entry.Value, writer, writer_is_private,
                               depth + 1);
                }
                writer.WriteObjectEnd();

                return;
            }

            Type obj_type = obj.GetType();

            // See if there's a custom exporter for the object
            if (custom_exporters_table.ContainsKey(obj_type))
            {
                ExporterFunc exporter = custom_exporters_table[obj_type];
                exporter(obj, writer);

                return;
            }

            // If not, maybe there's a base exporter
            if (base_exporters_table.ContainsKey(obj_type))
            {
                ExporterFunc exporter = base_exporters_table[obj_type];
                exporter(obj, writer);

                return;
            }

            // Last option, let's see if it's an enum
            if (obj is Enum)
            {
                Type e_type = Enum.GetUnderlyingType(obj_type);

                if (e_type == typeof(long) ||
                    e_type == typeof(uint) ||
                    e_type == typeof(ulong))
                {
                    writer.Write((ulong)obj);
                }
                else
                {
                    writer.Write((int)obj);
                }

                return;
            }

            // Okay, so it looks like the input should be exported as an
            // object
            AddTypeProperties(obj_type);
            IList <PropertyMetadata> props = type_properties[obj_type];

            writer.WriteObjectStart();
            foreach (PropertyMetadata p_data in props)
            {
                if (JsonIgnoreAttribute.Ignore(p_data.Info))
                {
                    continue;
                }

                JsonPropertyAttribute jsonPropertyAttribute = p_data.Info.GetCustomAttribute <JsonPropertyAttribute>();
                string propertyName = jsonPropertyAttribute == null ? p_data.Info.Name : jsonPropertyAttribute.JsonProperty;

                if (p_data.IsField)
                {
                    writer.WritePropertyName(propertyName);
                    WriteValue(((FieldInfo)p_data.Info).GetValue(obj),
                               writer, writer_is_private, depth + 1);
                }
                else
                {
                    PropertyInfo p_info = (PropertyInfo)p_data.Info;

                    if (p_info.CanRead)
                    {
                        writer.WritePropertyName(propertyName);
                        WriteValue(p_info.GetValue(obj, null),
                                   writer, writer_is_private, depth + 1);
                    }
                }
            }
            writer.WriteObjectEnd();
        }
        private static void AddObjectMetadata(Type type)
        {
            if (object_metadata.ContainsKey(type))
            {
                return;
            }

            ObjectMetadata data = new ObjectMetadata();

            if (type.GetInterface("System.Collections.IDictionary") != null)
            {
                data.IsDictionary = true;
            }

            data.Properties = new Dictionary <string, PropertyMetadata> ();

            foreach (PropertyInfo p_info in type.GetProperties())
            {
                if (p_info.Name == "Item")
                {
                    ParameterInfo[] parameters = p_info.GetIndexParameters();

                    if (parameters.Length != 1)
                    {
                        continue;
                    }

                    if (parameters[0].ParameterType == typeof(string))
                    {
                        data.ElementType = p_info.PropertyType;
                    }

                    continue;
                }

                if (!JsonIgnoreAttribute.Ignore(p_info))
                {
                    PropertyMetadata p_data = new PropertyMetadata();
                    p_data.Info = p_info;
                    p_data.Type = p_info.PropertyType;

                    JsonPropertyAttribute jsonPropertyAttribute = p_info.GetCustomAttribute <JsonPropertyAttribute>();
                    string propertyName = jsonPropertyAttribute == null ? p_info.Name : jsonPropertyAttribute.JsonProperty;
                    data.Properties.Add(propertyName, p_data);
                }
            }

            foreach (FieldInfo f_info in type.GetFields())
            {
                if (!JsonIgnoreAttribute.Ignore(f_info))
                {
                    PropertyMetadata p_data = new PropertyMetadata();
                    p_data.Info    = f_info;
                    p_data.IsField = true;
                    p_data.Type    = f_info.FieldType;

                    JsonPropertyAttribute jsonPropertyAttribute = f_info.GetCustomAttribute <JsonPropertyAttribute>();
                    string propertyName = jsonPropertyAttribute == null ? f_info.Name : jsonPropertyAttribute.JsonProperty;
                    data.Properties.Add(propertyName, p_data);
                }
            }

            lock (object_metadata_lock) {
                try {
                    object_metadata.Add(type, data);
                } catch (ArgumentException) {
                    return;
                }
            }
        }