using System;
|
using System.Collections;
|
using System.Collections.Generic;
|
#if !SILVERLIGHT
|
using System.Data;
|
#endif
|
using System.Globalization;
|
using System.IO;
|
using System.Text;
|
using System.Collections.Specialized;
|
using Top.Api;
|
using Top.Api.Util;
|
using System.Reflection;
|
using System.Xml.Serialization;
|
|
namespace FastJSON
|
{
|
internal sealed class JSONSerializer
|
{
|
private StringBuilder _output = new StringBuilder();
|
//private StringBuilder _before = new StringBuilder();
|
private int _before;
|
private int _MAX_DEPTH = 20;
|
int _current_depth = 0;
|
private Dictionary<string, int> _globalTypes = new Dictionary<string, int>();
|
private Dictionary<object, int> _cirobj = new Dictionary<object, int>();
|
private JSONParameters _params;
|
private bool _useEscapedUnicode = false;
|
|
internal JSONSerializer(JSONParameters param)
|
{
|
_params = param;
|
_useEscapedUnicode = _params.UseEscapedUnicode;
|
_MAX_DEPTH = _params.SerializerMaxDepth;
|
}
|
|
internal string ConvertToJSON(object obj)
|
{
|
WriteValue(obj);
|
|
if (_params.UsingGlobalTypes && _globalTypes != null && _globalTypes.Count > 0)
|
{
|
var sb = new StringBuilder();
|
sb.Append("\"$types\":{");
|
var pendingSeparator = false;
|
foreach (var kv in _globalTypes)
|
{
|
if (pendingSeparator) sb.Append(',');
|
pendingSeparator = true;
|
sb.Append('\"');
|
sb.Append(kv.Key);
|
sb.Append("\":\"");
|
sb.Append(kv.Value);
|
sb.Append('\"');
|
}
|
sb.Append("},");
|
_output.Insert(_before, sb.ToString());
|
}
|
return _output.ToString();
|
}
|
|
private void WriteValue(object obj)
|
{
|
if (obj == null || obj is DBNull)
|
_output.Append("null");
|
|
else if (obj is string || obj is char)
|
WriteString(obj.ToString());
|
|
else if (obj is Guid)
|
WriteGuid((Guid)obj);
|
|
else if (obj is bool)
|
_output.Append(((bool)obj) ? "true" : "false"); // conform to standard
|
|
else if (
|
obj is int || obj is long || obj is double ||
|
obj is decimal || obj is float ||
|
obj is byte || obj is short ||
|
obj is sbyte || obj is ushort ||
|
obj is uint || obj is ulong
|
)
|
_output.Append(((IConvertible)obj).ToString(NumberFormatInfo.InvariantInfo));
|
|
else if (obj is DateTime)
|
WriteDateTime((DateTime)obj);
|
|
else if (_params.KVStyleStringDictionary == false && obj is IDictionary &&
|
obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string))
|
|
WriteStringDictionary((IDictionary)obj);
|
#if net4
|
else if (_params.KVStyleStringDictionary == false && obj is System.Dynamic.ExpandoObject)
|
WriteStringDictionary((IDictionary<string, object>)obj);
|
#endif
|
else if (obj is IDictionary)
|
WriteStringDictionary((IDictionary)obj);
|
#if !SILVERLIGHT
|
else if (obj is DataSet)
|
WriteDataset((DataSet)obj);
|
|
else if (obj is DataTable)
|
this.WriteDataTable((DataTable)obj);
|
#endif
|
else if (obj is byte[])
|
WriteBytes((byte[])obj);
|
|
else if (obj is StringDictionary)
|
WriteSD((StringDictionary)obj);
|
|
else if (obj is NameValueCollection)
|
WriteNV((NameValueCollection)obj);
|
|
else if (obj is IEnumerable)
|
WriteArray((IEnumerable)obj);
|
|
else if (obj is Enum)
|
WriteEnum((Enum)obj);
|
|
else if (Reflection.Instance.IsTypeRegistered(obj.GetType()))
|
WriteCustom(obj);
|
|
else
|
WriteObject(obj);
|
}
|
|
private void WriteNV(NameValueCollection nameValueCollection)
|
{
|
_output.Append('{');
|
|
bool pendingSeparator = false;
|
|
foreach (string key in nameValueCollection)
|
{
|
if (_params.SerializeNullValues == false && (nameValueCollection[key] == null))
|
{
|
}
|
else
|
{
|
if (pendingSeparator) _output.Append(',');
|
if (_params.SerializeToLowerCaseNames)
|
WritePair(key.ToLower(), nameValueCollection[key]);
|
else
|
WritePair(key, nameValueCollection[key]);
|
pendingSeparator = true;
|
}
|
}
|
_output.Append('}');
|
}
|
|
private void WriteSD(StringDictionary stringDictionary)
|
{
|
_output.Append('{');
|
|
bool pendingSeparator = false;
|
|
foreach (DictionaryEntry entry in stringDictionary)
|
{
|
if (_params.SerializeNullValues == false && (entry.Value == null))
|
{
|
}
|
else
|
{
|
if (pendingSeparator) _output.Append(',');
|
|
string k = (string)entry.Key;
|
if (_params.SerializeToLowerCaseNames)
|
WritePair(k.ToLower(), entry.Value);
|
else
|
WritePair(k, entry.Value);
|
pendingSeparator = true;
|
}
|
}
|
_output.Append('}');
|
}
|
|
private void WriteCustom(object obj)
|
{
|
Serialize s;
|
Reflection.Instance._customSerializer.TryGetValue(obj.GetType(), out s);
|
WriteStringFast(s(obj));
|
}
|
|
private void WriteEnum(Enum e)
|
{
|
// TODO : optimize enum write
|
if (_params.UseValuesOfEnums)
|
WriteValue(Convert.ToInt32(e));
|
else
|
WriteStringFast(e.ToString());
|
}
|
|
private void WriteGuid(Guid g)
|
{
|
if (_params.UseFastGuid == false)
|
WriteStringFast(g.ToString());
|
else
|
WriteBytes(g.ToByteArray());
|
}
|
|
private void WriteBytes(byte[] bytes)
|
{
|
#if !SILVERLIGHT
|
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None));
|
#else
|
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length));
|
#endif
|
}
|
|
private void WriteDateTime(DateTime dateTime)
|
{
|
if (_params.UseApiNamingStyle)
|
{
|
WriteString(dateTime.ToString(Constants.DATE_TIME_FORMAT));
|
return;
|
}
|
// datetime format standard : yyyy-MM-dd HH:mm:ss
|
DateTime dt = dateTime;
|
if (_params.UseUTCDateTime)
|
dt = dateTime.ToUniversalTime();
|
|
_output.Append('\"');
|
_output.Append(dt.Year.ToString("0000", NumberFormatInfo.InvariantInfo));
|
_output.Append('-');
|
_output.Append(dt.Month.ToString("00", NumberFormatInfo.InvariantInfo));
|
_output.Append('-');
|
_output.Append(dt.Day.ToString("00", NumberFormatInfo.InvariantInfo));
|
_output.Append('T'); // strict ISO date compliance
|
_output.Append(dt.Hour.ToString("00", NumberFormatInfo.InvariantInfo));
|
_output.Append(':');
|
_output.Append(dt.Minute.ToString("00", NumberFormatInfo.InvariantInfo));
|
_output.Append(':');
|
_output.Append(dt.Second.ToString("00", NumberFormatInfo.InvariantInfo));
|
if (_params.DateTimeMilliseconds)
|
{
|
_output.Append('.');
|
_output.Append(dt.Millisecond.ToString("000", NumberFormatInfo.InvariantInfo));
|
}
|
if (_params.UseUTCDateTime)
|
_output.Append('Z');
|
|
_output.Append('\"');
|
}
|
|
#if !SILVERLIGHT
|
private DatasetSchema GetSchema(DataTable ds)
|
{
|
if (ds == null) return null;
|
|
DatasetSchema m = new DatasetSchema();
|
m.Info = new List<string>();
|
m.Name = ds.TableName;
|
|
foreach (DataColumn c in ds.Columns)
|
{
|
m.Info.Add(ds.TableName);
|
m.Info.Add(c.ColumnName);
|
m.Info.Add(c.DataType.ToString());
|
}
|
// FEATURE : serialize relations and constraints here
|
|
return m;
|
}
|
|
private DatasetSchema GetSchema(DataSet ds)
|
{
|
if (ds == null) return null;
|
|
DatasetSchema m = new DatasetSchema();
|
m.Info = new List<string>();
|
m.Name = ds.DataSetName;
|
|
foreach (DataTable t in ds.Tables)
|
{
|
foreach (DataColumn c in t.Columns)
|
{
|
m.Info.Add(t.TableName);
|
m.Info.Add(c.ColumnName);
|
m.Info.Add(c.DataType.ToString());
|
}
|
}
|
// FEATURE : serialize relations and constraints here
|
|
return m;
|
}
|
|
private string GetXmlSchema(DataTable dt)
|
{
|
using (var writer = new StringWriter())
|
{
|
dt.WriteXmlSchema(writer);
|
return dt.ToString();
|
}
|
}
|
|
private void WriteDataset(DataSet ds)
|
{
|
_output.Append('{');
|
if (_params.UseExtensions)
|
{
|
WritePair("$schema", _params.UseOptimizedDatasetSchema ? (object)GetSchema(ds) : ds.GetXmlSchema());
|
_output.Append(',');
|
}
|
bool tablesep = false;
|
foreach (DataTable table in ds.Tables)
|
{
|
if (tablesep) _output.Append(',');
|
tablesep = true;
|
WriteDataTableData(table);
|
}
|
// end dataset
|
_output.Append('}');
|
}
|
|
private void WriteDataTableData(DataTable table)
|
{
|
_output.Append('\"');
|
_output.Append(table.TableName);
|
_output.Append("\":[");
|
DataColumnCollection cols = table.Columns;
|
bool rowseparator = false;
|
foreach (DataRow row in table.Rows)
|
{
|
if (rowseparator) _output.Append(',');
|
rowseparator = true;
|
_output.Append('[');
|
|
bool pendingSeperator = false;
|
foreach (DataColumn column in cols)
|
{
|
if (pendingSeperator) _output.Append(',');
|
WriteValue(row[column]);
|
pendingSeperator = true;
|
}
|
_output.Append(']');
|
}
|
|
_output.Append(']');
|
}
|
|
void WriteDataTable(DataTable dt)
|
{
|
this._output.Append('{');
|
if (_params.UseExtensions)
|
{
|
this.WritePair("$schema", _params.UseOptimizedDatasetSchema ? (object)this.GetSchema(dt) : this.GetXmlSchema(dt));
|
this._output.Append(',');
|
}
|
|
WriteDataTableData(dt);
|
|
// end datatable
|
this._output.Append('}');
|
}
|
#endif
|
|
bool _TypesWritten = false;
|
private void WriteObject(object obj)
|
{
|
int i = 0;
|
if (_cirobj.TryGetValue(obj, out i) == false)
|
_cirobj.Add(obj, _cirobj.Count + 1);
|
else
|
{
|
if (_current_depth > 0 && _params.InlineCircularReferences == false)
|
{
|
//_circular = true;
|
_output.Append("{\"$i\":");
|
_output.Append(i.ToString());
|
_output.Append("}");
|
return;
|
}
|
}
|
if (_params.UsingGlobalTypes == false)
|
_output.Append('{');
|
else
|
{
|
if (_TypesWritten == false)
|
{
|
_output.Append('{');
|
_before = _output.Length;
|
//_output = new StringBuilder();
|
}
|
else
|
_output.Append('{');
|
}
|
_TypesWritten = true;
|
_current_depth++;
|
if (_current_depth > _MAX_DEPTH)
|
throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH);
|
|
|
Dictionary<string, string> map = new Dictionary<string, string>();
|
Type t = obj.GetType();
|
bool append = false;
|
if (_params.UseExtensions)
|
{
|
if (_params.UsingGlobalTypes == false)
|
WritePairFast("$type", Reflection.Instance.GetTypeAssemblyName(t));
|
else
|
{
|
int dt = 0;
|
string ct = Reflection.Instance.GetTypeAssemblyName(t);
|
if (_globalTypes.TryGetValue(ct, out dt) == false)
|
{
|
dt = _globalTypes.Count + 1;
|
_globalTypes.Add(ct, dt);
|
}
|
WritePairFast("$type", dt.ToString());
|
}
|
append = true;
|
}
|
|
Getters[] g = Reflection.Instance.GetGetters(t, _params.ShowReadOnlyProperties, _params.IgnoreAttributes);
|
int c = g.Length;
|
for (int ii = 0; ii < c; ii++)
|
{
|
var p = g[ii];
|
object o = p.Getter(obj);
|
|
String itemName = null;
|
PropertyInfo pi = t.GetProperty(p.Name);
|
XmlElementAttribute[] xeas = pi.GetCustomAttributes(typeof(XmlElementAttribute), true) as XmlElementAttribute[];
|
if (xeas != null && xeas.Length > 0)
|
{
|
itemName = xeas[0].ElementName;
|
}
|
|
// 获取列表属性名称
|
if (itemName == null)
|
{
|
XmlArrayAttribute[] xaas = pi.GetCustomAttributes(typeof(XmlArrayAttribute), true) as XmlArrayAttribute[];
|
if (xaas != null && xaas.Length > 0)
|
{
|
itemName = xaas[0].ElementName;
|
}
|
}
|
|
if (_params.SerializeNullValues == false && (o == null || o is DBNull))
|
{
|
//append = false;
|
}
|
else
|
{
|
if (append)
|
_output.Append(',');
|
if (_params.SerializeToLowerCaseNames)
|
WritePair(p.lcName, o, itemName);
|
else
|
WritePair(p.Name, o, itemName);
|
if (o != null && _params.UseExtensions)
|
{
|
Type tt = o.GetType();
|
if (tt == typeof(System.Object))
|
map.Add(p.Name, tt.ToString());
|
}
|
append = true;
|
}
|
}
|
if (map.Count > 0 && _params.UseExtensions)
|
{
|
_output.Append(",\"$map\":");
|
WriteStringDictionary(map);
|
}
|
_output.Append('}');
|
_current_depth--;
|
}
|
|
private void WritePairFast(string name, string value)
|
{
|
if (_params.UseApiNamingStyle)
|
{
|
string newName = StringUtil.ToUnderlineStyle(name.TrimEnd('_'));
|
WriteStringFast(newName);
|
}
|
else
|
{
|
WriteStringFast(name);
|
}
|
|
_output.Append(':');
|
|
WriteStringFast(value);
|
}
|
|
private void WritePair(string name, object value)
|
{
|
WritePair(name, value, null);
|
}
|
|
private void WritePair(string name, object value, String defaultName)
|
{
|
if (_params.UseApiNamingStyle)
|
{
|
if(defaultName != null)
|
{
|
WriteStringFast(defaultName);
|
}
|
else
|
{
|
string newName = StringUtil.ToUnderlineStyle(name.TrimEnd('_'));
|
WriteStringFast(newName);
|
}
|
}
|
else
|
{
|
WriteStringFast(name);
|
}
|
|
_output.Append(':');
|
|
WriteValue(value);
|
}
|
|
private void WriteArray(IEnumerable array)
|
{
|
_output.Append('[');
|
|
bool pendingSeperator = false;
|
|
foreach (object obj in array)
|
{
|
if (pendingSeperator) _output.Append(',');
|
|
WriteValue(obj);
|
|
pendingSeperator = true;
|
}
|
_output.Append(']');
|
}
|
|
private void WriteStringDictionary(IDictionary dic)
|
{
|
_output.Append('{');
|
|
bool pendingSeparator = false;
|
|
foreach (DictionaryEntry entry in dic)
|
{
|
if (_params.SerializeNullValues == false && (entry.Value == null))
|
{
|
}
|
else
|
{
|
if (pendingSeparator) _output.Append(',');
|
|
string k = (string)entry.Key;
|
if (_params.SerializeToLowerCaseNames)
|
WritePair(k.ToLower(), entry.Value);
|
else
|
WritePair(k, entry.Value);
|
pendingSeparator = true;
|
}
|
}
|
_output.Append('}');
|
}
|
|
private void WriteStringDictionary(IDictionary<string, object> dic)
|
{
|
_output.Append('{');
|
bool pendingSeparator = false;
|
foreach (KeyValuePair<string, object> entry in dic)
|
{
|
if (_params.SerializeNullValues == false && (entry.Value == null))
|
{
|
}
|
else
|
{
|
if (pendingSeparator) _output.Append(',');
|
string k = entry.Key;
|
|
if (_params.SerializeToLowerCaseNames)
|
WritePair(k.ToLower(), entry.Value);
|
else
|
WritePair(k, entry.Value);
|
pendingSeparator = true;
|
}
|
}
|
_output.Append('}');
|
}
|
|
private void WriteDictionary(IDictionary dic)
|
{
|
_output.Append('[');
|
|
bool pendingSeparator = false;
|
|
foreach (DictionaryEntry entry in dic)
|
{
|
if (pendingSeparator) _output.Append(',');
|
_output.Append('{');
|
WritePair("k", entry.Key);
|
_output.Append(",");
|
WritePair("v", entry.Value);
|
_output.Append('}');
|
|
pendingSeparator = true;
|
}
|
_output.Append(']');
|
}
|
|
private void WriteStringFast(string s)
|
{
|
_output.Append('\"');
|
_output.Append(s);
|
_output.Append('\"');
|
}
|
|
private void WriteString(string s)
|
{
|
_output.Append('\"');
|
|
int runIndex = -1;
|
int l = s.Length;
|
for (var index = 0; index < l; ++index)
|
{
|
var c = s[index];
|
|
if (_useEscapedUnicode)
|
{
|
if (c >= ' ' && c < 128 && c != '\"' && c != '\\')
|
{
|
if (runIndex == -1)
|
runIndex = index;
|
|
continue;
|
}
|
}
|
else
|
{
|
if (c != '\t' && c != '\n' && c != '\r' && c != '\"' && c != '\\')// && c != ':' && c!=',')
|
{
|
if (runIndex == -1)
|
runIndex = index;
|
|
continue;
|
}
|
}
|
|
if (runIndex != -1)
|
{
|
_output.Append(s, runIndex, index - runIndex);
|
runIndex = -1;
|
}
|
|
switch (c)
|
{
|
case '\t': _output.Append("\\t"); break;
|
case '\r': _output.Append("\\r"); break;
|
case '\n': _output.Append("\\n"); break;
|
case '"':
|
case '\\': _output.Append('\\'); _output.Append(c); break;
|
default:
|
if (_useEscapedUnicode)
|
{
|
_output.Append("\\u");
|
_output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
|
}
|
else
|
_output.Append(c);
|
|
break;
|
}
|
}
|
|
if (runIndex != -1)
|
_output.Append(s, runIndex, s.Length - runIndex);
|
|
_output.Append('\"');
|
}
|
}
|
}
|