修正DbTable的Json序列化问题,枚举类被当做数组去序列化了
大石头 authored at 2020-02-02 18:56:45
16.69 KiB
X
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Text;
using NewLife.Reflection;

namespace NewLife.Serialization
{
    /// <summary>Json写入器</summary>
    public class JsonWriter
    {
        #region 属性
        /// <summary>使用UTC时间。默认false</summary>
        public Boolean UseUTCDateTime { get; set; }

        /// <summary>使用小写名称</summary>
        public Boolean LowerCase { get; set; }

        /// <summary>使用驼峰命名</summary>
        public Boolean CamelCase { get; set; }

        /// <summary>忽略空值。默认false</summary>
        public Boolean IgnoreNullValues { get; set; }

        /// <summary>忽略只读属性。默认false</summary>
        public Boolean IgnoreReadOnlyProperties { get; set; }

        /// <summary>忽略注释。默认true</summary>
        public Boolean IgnoreComment { get; set; } = true;

        /// <summary>枚举使用字符串。默认false使用数字</summary>
        public Boolean EnumString { get; set; }

        /// <summary>缩进。默认false</summary>
        public Boolean Indented { get; set; }

        /// <summary>最大序列化深度。默认5</summary>
        public Int32 MaxDepth { get; set; } = 5;

        private StringBuilder _Builder = new StringBuilder();
        #endregion

        #region 构造
        /// <summary>实例化</summary>
        public JsonWriter() { }
        #endregion

        #region 静态转换
        /// <summary>对象序列化为Json字符串</summary>
        /// <param name="obj"></param>
        /// <param name="indented">是否缩进。默认false</param>
        /// <param name="nullValue">是否写控制。默认true</param>
        /// <param name="camelCase">是否驼峰命名。默认false</param>
        /// <returns></returns>
        public static String ToJson(Object obj, Boolean indented = false, Boolean nullValue = true, Boolean camelCase = false)
        {
            var jw = new JsonWriter
            {
                IgnoreNullValues = !nullValue,
                CamelCase = camelCase,
                Indented = indented,
            };

            jw.WriteValue(obj);

            var json = jw._Builder.ToString();
            //if (indented) json = JsonHelper.Format(json);

            return json;
        }
        #endregion

        #region 写入方法
        /// <summary>写入对象</summary>
        /// <param name="value"></param>
        public void Write(Object value) => WriteValue(value);

        /// <summary>获取结果</summary>
        /// <returns></returns>
        public String GetString() => _Builder.ToString();

        private void WriteValue(Object obj)
        {
            if (obj == null || obj is DBNull)
                _Builder.Append("null");

            else if (obj is String || obj is Char)
                WriteString(obj + "");

            else if (obj is Guid)
                WriteStringFast(obj + "");

            else if (obj is Boolean)
                _Builder.Append((obj + "").ToLower());

            else if (
                obj is Int32 || obj is Int64 || obj is Double ||
                obj is Decimal || obj is Single ||
                obj is Byte || obj is Int16 ||
                obj is SByte || obj is UInt16 ||
                obj is UInt32 || obj is UInt64
            )
                _Builder.Append(((IConvertible)obj).ToString(NumberFormatInfo.InvariantInfo));

            else if (obj is TimeSpan)
                WriteString(obj + "");

            else if (obj is DateTime)
                WriteDateTime((DateTime)obj);

            else if (obj is IDictionary<String, Object> sdic)
                WriteStringDictionary(sdic);
            else if (obj is IDictionary && obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(String))
                WriteStringDictionary((IDictionary)obj);
            else if (obj is System.Dynamic.ExpandoObject)
                WriteStringDictionary((IDictionary<String, Object>)obj);
            else if (obj is IDictionary)
                WriteDictionary((IDictionary)obj);
            else if (obj is Byte[] buf)
            {
                WriteStringFast(Convert.ToBase64String(buf, 0, buf.Length, Base64FormattingOptions.None));
            }

            else if (obj is StringDictionary)
                WriteSD((StringDictionary)obj);

            else if (obj is NameValueCollection)
                WriteNV((NameValueCollection)obj);

            //else if (obj.GetType().IsArray)
            //    WriteArray((IEnumerable)obj);
            else if (obj is IList list)
                WriteArray(list);

            else if (obj is Enum)
            {
                if (EnumString)
                    WriteValue(obj + "");
                else
                    WriteValue(Convert.ToInt32(obj));
            }

            else
                WriteObject(obj);
        }

        private void WriteNV(NameValueCollection nvs)
        {
            _Builder.Append('{');
            WriteLeftIndent();

            var first = true;

            foreach (String item in nvs)
            {
                if (!IgnoreNullValues || !IsNull(nvs[item]))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    var name = FormatName(item);
                    WritePair(name, nvs[item]);
                }
            }

            WriteRightIndent();
            _Builder.Append('}');
        }

        private void WriteSD(StringDictionary dic)
        {
            _Builder.Append('{');
            WriteLeftIndent();

            var first = true;

            foreach (DictionaryEntry item in dic)
            {
                if (!IgnoreNullValues || !IsNull(item.Value))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    var name = FormatName((String)item.Key);
                    WritePair(name, item.Value);
                }
            }

            WriteRightIndent();
            _Builder.Append('}');
        }

        private void WriteDateTime(DateTime dateTime)
        {
            var dt = dateTime;
            if (UseUTCDateTime) dt = dateTime.ToUniversalTime();

            // 纯日期缩短长度
            var str = "";
            if (dt.Year > 1000)
            {
                if (dt.Hour == 0 && dt.Minute == 0 && dt.Second == 0)
                {
                    str = dt.ToString("yyyy-MM-dd");

                    // 处理UTC
                    if (dt.Kind == DateTimeKind.Utc) str += " UTC";
                }
                else
                    str = dt.ToFullString();
            }

            _Builder.AppendFormat("\"{0}\"", str);
        }

        Int32 _depth = 0;
        private Dictionary<Object, Int32> _cirobj = new Dictionary<Object, Int32>();
        private void WriteObject(Object obj)
        {
            if (!_cirobj.TryGetValue(obj, out _)) _cirobj.Add(obj, _cirobj.Count + 1);

            _Builder.Append('{');
            WriteLeftIndent();

            _depth++;
            if (_depth > MaxDepth) throw new Exception("超过了序列化最大深度 " + MaxDepth);

            var t = obj.GetType();

            var first = true;
            foreach (var pi in t.GetProperties(true))
            {
                if (IgnoreReadOnlyProperties && pi.CanRead && !pi.CanWrite) continue;

                var value = obj.GetValue(pi);
                if (!IgnoreNullValues || !IsNull(value))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    var name = FormatName(SerialHelper.GetName(pi));

                    // 注释
                    if (!IgnoreComment && Indented)
                    {
                        var comment = pi.GetDisplayName() ?? pi.GetDescription();
                        if (!comment.IsNullOrEmpty())
                        {
                            _Builder.AppendFormat("// {0}", comment);
                            WriteIndent();
                        }
                    }

                    WritePair(name, value);
                }
            }

            WriteRightIndent();
            _Builder.Append('}');
            _depth--;
        }
        #endregion

        #region 辅助
        private void WritePairFast(String name, String value)
        {
            WriteStringFast(name);

            _Builder.Append(':');

            WriteStringFast(value);
        }

        private void WritePair(String name, Object value)
        {
            WriteStringFast(name);

            _Builder.Append(':');
            if (Indented) _Builder.Append(' ');

            WriteValue(value);
        }

        private void WriteArray(IEnumerable arr)
        {
            _Builder.Append('[');
            WriteLeftIndent();

            var first = true;
            foreach (var obj in arr)
            {
                if (!first)
                {
                    _Builder.Append(',');
                    WriteIndent();
                }
                first = false;

                WriteValue(obj);
            }

            WriteRightIndent();
            _Builder.Append(']');
        }

        private void WriteStringDictionary(IDictionary dic)
        {
            _Builder.Append('{');
            WriteLeftIndent();

            var first = true;
            foreach (DictionaryEntry item in dic)
            {
                if (!IgnoreNullValues || !IsNull(item.Value))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    var name = FormatName((String)item.Key);
                    WritePair(name, item.Value);
                }
            }

            WriteRightIndent();
            _Builder.Append('}');
        }

        private void WriteStringDictionary(IDictionary<String, Object> dic)
        {
            _Builder.Append('{');
            WriteLeftIndent();

            var first = true;
            foreach (var item in dic)
            {
                // 跳过注释
                if (item.Key[0] == '#') continue;

                if (!IgnoreNullValues || !IsNull(item.Value))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    var name = FormatName(item.Key);

                    // 注释
                    if (!IgnoreComment && Indented && dic.TryGetValue("#" + name, out var comment) && comment != null)
                    {
                        WritePair("#" + name, comment);
                        _Builder.Append(',');
                        //_Builder.AppendFormat("// {0}", comment);
                        WriteIndent();
                    }

                    WritePair(name, item.Value);
                }
            }

            WriteRightIndent();
            _Builder.Append('}');
        }

        private void WriteDictionary(IDictionary dic)
        {
            _Builder.Append('[');
            WriteLeftIndent();

            var first = true;
            foreach (DictionaryEntry entry in dic)
            {
                if (!IgnoreNullValues || !IsNull(entry.Value))
                {
                    if (!first)
                    {
                        _Builder.Append(',');
                        WriteIndent();
                    }
                    first = false;

                    _Builder.Append('{');
                    WriteLeftIndent();
                    WritePair("k", entry.Key);
                    _Builder.Append(",");
                    WriteIndent();
                    WritePair("v", entry.Value);
                    WriteRightIndent();
                    _Builder.Append('}');
                }
            }

            WriteRightIndent();
            _Builder.Append(']');
        }

        private void WriteStringFast(String str)
        {
            _Builder.Append('\"');
            _Builder.Append(str);
            _Builder.Append('\"');
        }

        private void WriteString(String str)
        {
            _Builder.Append('\"');

            var idx = -1;
            var len = str.Length;
            for (var index = 0; index < len; ++index)
            {
                var c = str[index];

                if (c != '\t' && c != '\n' && c != '\r' && c != '\"' && c != '\\')// && c != ':' && c!=',')
                {
                    if (idx == -1) idx = index;

                    continue;
                }

                if (idx != -1)
                {
                    _Builder.Append(str, idx, index - idx);
                    idx = -1;
                }

                switch (c)
                {
                    case '\t': _Builder.Append("\\t"); break;
                    case '\r': _Builder.Append("\\r"); break;
                    case '\n': _Builder.Append("\\n"); break;
                    case '"':
                    case '\\': _Builder.Append('\\'); _Builder.Append(c); break;
                    default:
                        _Builder.Append(c);

                        break;
                }
            }

            if (idx != -1) _Builder.Append(str, idx, str.Length - idx);

            _Builder.Append('\"');
        }

        /// <summary>根据小写和驼峰格式化名称</summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private String FormatName(String name)
        {
            if (name.IsNullOrEmpty()) return name;

            if (LowerCase) return name.ToLower();
            if (CamelCase) return name.Substring(0, 1).ToLower() + name.Substring(1);

            return name;
        }

        private static IDictionary<TypeCode, Object> _def;
        private static Boolean IsNull(Object obj)
        {
            if (obj == null || obj is DBNull) return true;

            var code = obj.GetType().GetTypeCode();
            if (code == TypeCode.Object) return false;
            if (code == TypeCode.Empty || code == TypeCode.DBNull) return true;

            var dic = _def;
            if (dic == null)
            {
                dic = new Dictionary<TypeCode, Object>
                {
                    [TypeCode.Boolean] = false,
                    [TypeCode.Char] = '\0',
                    [TypeCode.SByte] = (SByte)0,
                    [TypeCode.Byte] = (Byte)0,
                    [TypeCode.Int16] = (Int16)0,
                    [TypeCode.UInt16] = (UInt16)0,
                    [TypeCode.Int32] = 0,
                    [TypeCode.UInt32] = (UInt32)0,
                    [TypeCode.Int64] = (Int64)0,
                    [TypeCode.UInt64] = (UInt64)0,
                    [TypeCode.Single] = (Single)0,
                    [TypeCode.Double] = (Double)0,
                    [TypeCode.Decimal] = (Decimal)0,
                    [TypeCode.DateTime] = DateTime.MinValue,
                    [TypeCode.String] = "",
                };

                _def = dic;
            }

            return dic.TryGetValue(code, out var rs) && Equals(obj, rs);
        }
        #endregion

        #region 缩进
        /// <summary>当前缩进层级</summary>
        private Int32 _level;

        private void WriteIndent()
        {
            if (!Indented) return;

            _Builder.AppendLine();
            _Builder.Append(' ', _level * 4);
        }

        private void WriteLeftIndent()
        {
            if (!Indented) return;

            _Builder.AppendLine();
            _Builder.Append(' ', ++_level * 4);
        }

        private void WriteRightIndent()
        {
            if (!Indented) return;

            _Builder.AppendLine();
            _Builder.Append(' ', --_level * 4);
        }
        #endregion
    }
}