520 lines
15 KiB
C#
520 lines
15 KiB
C#
//http://fastjson.codeplex.com/
|
|
//http://fastjson.codeplex.com/license
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Text;
|
|
|
|
namespace Exceptron.Client.fastJSON
|
|
{
|
|
internal class JSONSerializer
|
|
{
|
|
private readonly StringBuilder _output = new StringBuilder();
|
|
readonly bool useMinimalDataSetSchema;
|
|
readonly bool fastguid = true;
|
|
readonly bool useExtension = true;
|
|
readonly bool serializeNulls = true;
|
|
readonly int _MAX_DEPTH = 10;
|
|
readonly bool _Indent;
|
|
readonly bool _useGlobalTypes = true;
|
|
int _current_depth;
|
|
private readonly Dictionary<string, int> _globalTypes = new Dictionary<string, int>();
|
|
|
|
internal JSONSerializer(bool UseMinimalDataSetSchema, bool UseFastGuid, bool UseExtensions, bool SerializeNulls, bool IndentOutput)
|
|
{
|
|
useMinimalDataSetSchema = UseMinimalDataSetSchema;
|
|
fastguid = UseFastGuid;
|
|
useExtension = UseExtensions;
|
|
_Indent = IndentOutput;
|
|
serializeNulls = SerializeNulls;
|
|
if (useExtension == false)
|
|
_useGlobalTypes = false;
|
|
}
|
|
|
|
internal string ConvertToJSON(object obj)
|
|
{
|
|
WriteValue(obj);
|
|
|
|
string str = "";
|
|
if (_useGlobalTypes)
|
|
{
|
|
StringBuilder sb = new StringBuilder();
|
|
sb.Append("{\"$types\":{");
|
|
bool 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("},");
|
|
str = sb + _output.ToString();
|
|
}
|
|
else
|
|
str = _output.ToString();
|
|
|
|
return str;
|
|
}
|
|
|
|
private void WriteValue(object obj)
|
|
{
|
|
if (obj == null || obj is DBNull)
|
|
_output.Append("null");
|
|
|
|
else if (obj is string || obj is char)
|
|
WriteString((string)obj);
|
|
|
|
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 (obj is IDictionary && obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string))
|
|
WriteStringDictionary((IDictionary)obj);
|
|
|
|
else if (obj is IDictionary)
|
|
WriteDictionary((IDictionary)obj);
|
|
#if !SILVERLIGHT
|
|
else if (obj is DataSet)
|
|
WriteDataset((DataSet)obj);
|
|
|
|
else if (obj is DataTable)
|
|
WriteDataTable((DataTable)obj);
|
|
#endif
|
|
else if (obj is byte[])
|
|
WriteBytes((byte[])obj);
|
|
|
|
else if (obj is Array || obj is IList || obj is ICollection)
|
|
WriteArray((IEnumerable)obj);
|
|
|
|
else if (obj is Enum)
|
|
WriteEnum((Enum)obj);
|
|
|
|
#if CUSTOMTYPE
|
|
else if (JSON.Instance.IsTypeRegistered(obj.GetType()))
|
|
WriteCustom(obj);
|
|
#endif
|
|
else
|
|
WriteObject(obj);
|
|
}
|
|
|
|
#if CUSTOMTYPE
|
|
private void WriteCustom(object obj)
|
|
{
|
|
Serialize s;
|
|
JSON.Instance._customSerializer.TryGetValue(obj.GetType(), out s);
|
|
WriteStringFast(s(obj));
|
|
}
|
|
#endif
|
|
|
|
private void WriteEnum(Enum e)
|
|
{
|
|
// TODO : optimize enum write
|
|
WriteStringFast(e.ToString());
|
|
}
|
|
|
|
private void WriteGuid(Guid g)
|
|
{
|
|
if (fastguid == 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)
|
|
{
|
|
// datetime format standard : yyyy-MM-dd HH:mm:ss
|
|
DateTime dt = dateTime;
|
|
if (JSON.Instance.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(" ");
|
|
_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 (JSON.Instance.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());
|
|
}
|
|
// TODO : 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());
|
|
}
|
|
}
|
|
// TODO : 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 (useExtension)
|
|
{
|
|
WritePair("$schema", useMinimalDataSetSchema ? (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)
|
|
{
|
|
_output.Append('{');
|
|
if (useExtension)
|
|
{
|
|
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(dt) : GetXmlSchema(dt));
|
|
_output.Append(',');
|
|
}
|
|
|
|
WriteDataTableData(dt);
|
|
|
|
// end datatable
|
|
_output.Append('}');
|
|
}
|
|
#endif
|
|
bool _firstWritten;
|
|
private void WriteObject(object obj)
|
|
{
|
|
Indent();
|
|
if (_useGlobalTypes == false)
|
|
_output.Append('{');
|
|
else
|
|
{
|
|
if (_firstWritten)
|
|
_output.Append("{");
|
|
}
|
|
_firstWritten = true;
|
|
_current_depth++;
|
|
if (_current_depth > _MAX_DEPTH)
|
|
throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH);
|
|
|
|
|
|
var map = new Dictionary<string, string>();
|
|
Type t = obj.GetType();
|
|
bool append = false;
|
|
if (useExtension)
|
|
{
|
|
if (_useGlobalTypes == false)
|
|
WritePairFast("$type", JSON.Instance.GetTypeAssemblyName(t));
|
|
else
|
|
{
|
|
int dt = 0;
|
|
string ct = JSON.Instance.GetTypeAssemblyName(t);
|
|
if (_globalTypes.TryGetValue(ct, out dt) == false)
|
|
{
|
|
dt = _globalTypes.Count + 1;
|
|
_globalTypes.Add(ct, dt);
|
|
}
|
|
WritePairFast("$type", dt.ToString());
|
|
}
|
|
append = true;
|
|
}
|
|
|
|
var g = JSON.Instance.GetGetters(t);
|
|
foreach (var p in g)
|
|
{
|
|
if (append)
|
|
_output.Append(',');
|
|
object o = p.Getter(obj);
|
|
if ((o == null || o is DBNull) && serializeNulls == false)
|
|
append = false;
|
|
else
|
|
{
|
|
WritePair(p.Name, o);
|
|
if (o != null && useExtension)
|
|
{
|
|
Type tt = o.GetType();
|
|
if (tt == typeof(Object))
|
|
map.Add(p.Name, tt.ToString());
|
|
}
|
|
append = true;
|
|
}
|
|
}
|
|
if (map.Count > 0 && useExtension)
|
|
{
|
|
_output.Append(",\"$map\":");
|
|
WriteStringDictionary(map);
|
|
}
|
|
_current_depth--;
|
|
Indent();
|
|
_output.Append('}');
|
|
_current_depth--;
|
|
|
|
}
|
|
|
|
private void Indent()
|
|
{
|
|
Indent(false);
|
|
}
|
|
|
|
private void Indent(bool dec)
|
|
{
|
|
if (_Indent)
|
|
{
|
|
_output.Append("\r\n");
|
|
for (int i = 0; i < _current_depth - (dec ? 1 : 0); i++)
|
|
_output.Append("\t");
|
|
}
|
|
}
|
|
|
|
private void WritePairFast(string name, string value)
|
|
{
|
|
if ((value == null) && serializeNulls == false)
|
|
return;
|
|
Indent();
|
|
WriteStringFast(name);
|
|
|
|
_output.Append(':');
|
|
|
|
WriteStringFast(value);
|
|
}
|
|
|
|
private void WritePair(string name, object value)
|
|
{
|
|
if ((value == null || value is DBNull) && serializeNulls == false)
|
|
return;
|
|
Indent();
|
|
WriteStringFast(name);
|
|
|
|
_output.Append(':');
|
|
|
|
WriteValue(value);
|
|
}
|
|
|
|
private void WriteArray(IEnumerable array)
|
|
{
|
|
Indent();
|
|
_output.Append('[');
|
|
|
|
bool pendingSeperator = false;
|
|
|
|
foreach (var obj in array)
|
|
{
|
|
Indent();
|
|
if (pendingSeperator) _output.Append(',');
|
|
|
|
WriteValue(obj);
|
|
|
|
pendingSeperator = true;
|
|
}
|
|
Indent();
|
|
_output.Append(']');
|
|
}
|
|
|
|
private void WriteStringDictionary(IDictionary dic)
|
|
{
|
|
Indent();
|
|
_output.Append('{');
|
|
|
|
bool pendingSeparator = false;
|
|
|
|
foreach (DictionaryEntry entry in dic)
|
|
{
|
|
if (pendingSeparator) _output.Append(',');
|
|
|
|
WritePair((string)entry.Key, entry.Value);
|
|
|
|
pendingSeparator = true;
|
|
}
|
|
Indent();
|
|
_output.Append('}');
|
|
}
|
|
|
|
private void WriteDictionary(IDictionary dic)
|
|
{
|
|
Indent();
|
|
_output.Append('[');
|
|
|
|
bool pendingSeparator = false;
|
|
|
|
foreach (DictionaryEntry entry in dic)
|
|
{
|
|
if (pendingSeparator) _output.Append(',');
|
|
Indent();
|
|
_output.Append('{');
|
|
WritePair("k", entry.Key);
|
|
_output.Append(",");
|
|
WritePair("v", entry.Value);
|
|
Indent();
|
|
_output.Append('}');
|
|
|
|
pendingSeparator = true;
|
|
}
|
|
Indent();
|
|
_output.Append(']');
|
|
}
|
|
|
|
private void WriteStringFast(string s)
|
|
{
|
|
//Indent();
|
|
_output.Append('\"');
|
|
_output.Append(s);
|
|
_output.Append('\"');
|
|
}
|
|
|
|
private void WriteString(string s)
|
|
{
|
|
//Indent();
|
|
_output.Append('\"');
|
|
|
|
int runIndex = -1;
|
|
|
|
for (var index = 0; index < s.Length; ++index)
|
|
{
|
|
var c = s[index];
|
|
|
|
if (c >= ' ' && c < 128 && 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:
|
|
_output.Append("\\u");
|
|
_output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (runIndex != -1)
|
|
{
|
|
_output.Append(s, runIndex, s.Length - runIndex);
|
|
}
|
|
|
|
_output.Append('\"');
|
|
}
|
|
}
|
|
}
|