private/runtime/JsonObjectExtensions.cs

namespace Carbon.Json
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;

    public partial class JsonObject
    {
        public void SafeAdd(string name, Func<JsonNode> valueFn)
        {
            if (valueFn != null)
            {
                var value = valueFn();
                if (null != value)
                {
                    items.Add(name, value);
                }
            }
        }

        public void SafeAdd(string name, JsonNode value)
        {
            if (null != value)
            {
                items.Add(name, value);
            }
        }

        public T NullableProperty<T>(string propertyName) where T : JsonNode
        {
            if (this.TryGetValue(propertyName, out JsonNode value))
            {
                if (value.IsNull)
                {
                    return null;
                }
                if (value is T tval)
                {
                    return tval;
                }
                /* it's present, but not the correct type... */
                //throw new Exception($"Property {propertyName} in object expected type {typeof(T).Name} but value of type {value.Type.ToString()} was found.");
            }
            return null;
        }

        public JsonObject Property(string propertyName)
        {
            return PropertyT<JsonObject>(propertyName);
        }

        public T PropertyT<T>(string propertyName) where T : JsonNode
        {
            if (this.TryGetValue(propertyName, out JsonNode value))
            {
                if (value.IsNull)
                {
                    return null; // we're going to assume that the consumer knows what to do if null is explicity returned?
                }

                if (value is T tval)
                {
                    return tval;
                }
                /* it's present, but not the correct type... */
                // throw new Exception($"Property {propertyName} in object expected type {typeof(T).Name} but value of type {value.Type.ToString()} was found.");
            }
            return null;
        }

        public int NumberProperty(string propertyName, ref int output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToInt() ?? output;
        public float NumberProperty(string propertyName, ref float output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToFloat() ?? output;
        public byte NumberProperty(string propertyName, ref byte output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToByte() ?? output;
        public long NumberProperty(string propertyName, ref long output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToLong() ?? output;
        public double NumberProperty(string propertyName, ref double output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToDouble() ?? output;
        public decimal NumberProperty(string propertyName, ref decimal output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToDecimal() ?? output;
        public short NumberProperty(string propertyName, ref short output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToShort() ?? output;
        public DateTime NumberProperty(string propertyName, ref DateTime output) => output = this.PropertyT<JsonNumber>(propertyName)?.ToDateTime() ?? output;

        public int? NumberProperty(string propertyName, ref int? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToInt() ?? null;
        public float? NumberProperty(string propertyName, ref float? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToFloat() ?? null;
        public byte? NumberProperty(string propertyName, ref byte? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToByte() ?? null;
        public long? NumberProperty(string propertyName, ref long? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToLong() ?? null;
        public double? NumberProperty(string propertyName, ref double? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToDouble() ?? null;
        public decimal? NumberProperty(string propertyName, ref decimal? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToDecimal() ?? null;
        public short? NumberProperty(string propertyName, ref short? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToShort() ?? null;

        public DateTime? NumberProperty(string propertyName, ref DateTime? output) => output = this.NullableProperty<JsonNumber>(propertyName)?.ToDateTime() ?? null;


        public string StringProperty(string propertyName) => this.PropertyT<JsonString>(propertyName)?.ToString();
        public string StringProperty(string propertyName, ref string output) => output = this.PropertyT<JsonString>(propertyName)?.ToString() ?? output;
        public char StringProperty(string propertyName, ref char output) => output = this.PropertyT<JsonString>(propertyName)?.ToChar() ?? output;
        public char? StringProperty(string propertyName, ref char? output) => output = this.PropertyT<JsonString>(propertyName)?.ToChar() ?? null;

        public DateTime StringProperty(string propertyName, ref DateTime output) => DateTime.TryParse(this.PropertyT<JsonString>(propertyName)?.ToString(), out output) ? output : output;
        public DateTime? StringProperty(string propertyName, ref DateTime? output) => output = DateTime.TryParse(this.PropertyT<JsonString>(propertyName)?.ToString(), out var o) ? o : output;


        public bool BooleanProperty(string propertyName, ref bool output) => output = this.PropertyT<JsonBoolean>(propertyName)?.ToBoolean() ?? output;
        public bool? BooleanProperty(string propertyName, ref bool? output) => output = this.PropertyT<JsonBoolean>(propertyName)?.ToBoolean() ?? null;

        public T[] ArrayProperty<T>(string propertyName, ref T[] output, Func<JsonNode, T> deserializer)
        {
            var array = this.PropertyT<JsonArray>(propertyName);
            if (array != null)
            {
                output = new T[array.Count];
                for (var i = 0; i < output.Length; i++)
                {
                    output[i] = deserializer(array[i]);
                }
            }
            return output;
        }

        public Dictionary<string, T> DictionaryProperty<T>(string propertyName, ref Dictionary<string, T> output, Func<JsonNode, T> deserializer)
        {
            var dictionary = this.PropertyT<JsonObject>(propertyName);
            if (output == null)
            {
                output = new Dictionary<string, T>();
            }
            else
            {
                output.Clear();
            }
            if (dictionary != null)
            {
                foreach (var key in dictionary.Keys)
                {
                    output[key] = deserializer(dictionary[key]);
                }
            }
            return output;
        }

        public static JsonObject Create<T>(IDictionary<string, T> source, Func<T, JsonNode> selector)
        {
            if (source == null || selector == null)
            {
                return null;
            }
            var result = new JsonObject();

            foreach (var key in source.Keys)
            {
                result.SafeAdd(key, selector(source[key]));
            }
            return result;
        }
    }

    public class foo<T> where T : new()
    {

    }

    public partial class JsonString
    {
        public static string DateFormat = "yyyy-MM-dd";
        public static string DateTimeFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK";
        public static string DateTimeRfc1123Format = "R";

        public static JsonString Create(string value) => value == null ? null : new JsonString(value);
        public static JsonString Create(char? value) => value is char c ? new JsonString(c.ToString()) : null;

        public static JsonString CreateDate(DateTime? value) => value is DateTime date ? new JsonString(date.ToString(DateFormat, CultureInfo.CurrentCulture)) : null;
        public static JsonString CreateDateTime(DateTime? value) => value is DateTime date ? new JsonString(date.ToString(DateTimeFormat, CultureInfo.CurrentCulture)) : null;
        public static JsonString CreateDateTimeRfc1123(DateTime? value) => value is DateTime date ? new JsonString(date.ToString(DateTimeRfc1123Format, CultureInfo.CurrentCulture)) : null;

        public char ToChar() => this.Value?.ToString()?.FirstOrDefault() ?? default(char);
        public static implicit operator char(JsonString value) => value?.ToString()?.FirstOrDefault() ?? default(char);
        public static implicit operator char? (JsonString value) => value?.ToString()?.FirstOrDefault();

        public static implicit operator DateTime(JsonString value) => DateTime.TryParse(value, out var output) ? output : default(DateTime);
        public static implicit operator DateTime? (JsonString value) => DateTime.TryParse(value, out var output) ? output : default(DateTime?);

    }
    public partial class JsonBoolean
    {
        public static JsonBoolean Create(bool? value) => value is bool b ? new JsonBoolean(b) : null;
        public bool ToBoolean() => Value;

    }
    public partial class JsonNumber
    {
        public static readonly DateTime EpochDate = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
        private static long ToUnixTime(DateTime dateTime)
        {
            return (long)dateTime.Subtract(EpochDate).TotalSeconds;
        }
        private static DateTime FromUnixTime(long totalSeconds)
        {
            return EpochDate.AddSeconds(totalSeconds);
        }
        public byte ToByte() => this;
        public int ToInt() => this;
        public long ToLong() => this;
        public short ToShort() => this;
        public UInt16 ToUInt16() => this;
        public UInt32 ToUInt32() => this;
        public UInt64 ToUInt64() => this;
        public decimal ToDecimal() => this;
        public double ToDouble() => this;
        public float ToFloat() => this;

        public static JsonNumber Create(int? value) => value is int n ? new JsonNumber(n) : null;
        public static JsonNumber Create(long? value) => value is long n ? new JsonNumber(n) : null;
        public static JsonNumber Create(float? value) => value is float n ? new JsonNumber(n) : null;
        public static JsonNumber Create(double? value) => value is double n ? new JsonNumber(n) : null;
        public static JsonNumber Create(decimal? value) => value is decimal n ? new JsonNumber(n) : null;
        public static JsonNumber Create(DateTime? value) => value is DateTime date ? new JsonNumber(ToUnixTime(date)) : null;

        public static implicit operator DateTime(JsonNumber number) => FromUnixTime(number);
        public DateTime ToDateTime() => this;

        public JsonNumber(decimal value)
        {
            this.value = value.ToString();
        }
    }

    public partial class XNodeArray
    {
        public static XNodeArray Create<T>(T[] source, Func<T, JsonNode> selector)
        {
            if (source == null || selector == null)
            {
                return null;
            }
            var result = new XNodeArray();
            foreach (var item in source.Select(selector))
            {
                result.SafeAdd(item);
            }
            return result;
        }
        public void SafeAdd(JsonNode item)
        {
            if (item != null)
            {
                items.Add(item);
            }
        }
        public void SafeAdd(Func<JsonNode> itemFn)
        {
            if (itemFn != null)
            {
                var item = itemFn();
                if (item != null)
                {
                    items.Add(item);
                }
            }
        }
    }


}