1. 程式人生 > >一種簡單,輕量,靈活的C#物件轉Json物件的方案

一種簡單,輕量,靈活的C#物件轉Json物件的方案

簡單,是因為只有一個類

輕量,是因為整個類程式碼只有300行

靈活,是因為擴充套件方式只需要繼承重寫某個方法即可

 

首先我將這個類稱之為JsonBuilder,我希望它以StringBuilder的方式來實現Json字串的轉換

複製程式碼
public class JsonBuilder
{
    protected StringBuilder Buff = new StringBuilder(4096);//字元緩衝區
    public string ToJsonString(object obj)
    {
        .......
        return Buff.ToString();
    }
    .......
}
複製程式碼

然後我為希望為每一個基礎型別單獨完成一個方法,並且方法可以被重寫

複製程式碼
protected virtual void AppendByte(Byte value)
protected virtual void AppendDecimal(Decimal value)
protected virtual void AppendInt16(Int16 value)
protected virtual void AppendInt32(Int32 value)
protected virtual void AppendInt64(Int64 value)
protected virtual void AppendSByte(SByte value)
protected virtual void AppendUInt16(UInt16 value)
protected virtual void AppendUInt32(UInt32 value)
protected virtual void AppendUInt64(UInt64 value)
protected virtual void AppendBoolean(Boolean value)
protected virtual void AppendChar(Char value)
protected virtual void AppendString(String value)
protected virtual void AppendDateTime(DateTime value)
protected virtual void AppendGuid(Guid value)
protected virtual void AppendDouble(Double value)
protected virtual void AppendSingle(Single value)
protected virtual void AppendEnum(Enum value)
複製程式碼

為了使子類重寫時更方便,我將數字型別合併為一個

protected virtual void AppendNumber(IConvertible number)

但仍然保留原有方法,只是原有方法直接呼叫AppendNumber,就像這樣

複製程式碼
protected virtual void AppendByte(Byte value) { AppendNumber(value); }
protected virtual void AppendDecimal(Decimal value) { AppendNumber(value); }
protected virtual void AppendInt16(Int16 value) { AppendNumber(value); }
protected virtual void AppendInt32(Int32 value) { AppendNumber(value); }
protected virtual void AppendInt64(Int64 value) { AppendNumber(value); }
protected virtual void AppendSByte(SByte value) { AppendNumber(value); }
protected virtual void AppendUInt16(UInt16 value) { AppendNumber(value); }
protected virtual void AppendUInt32(UInt32 value) { AppendNumber(value); }
protected virtual void AppendUInt64(UInt64 value) { AppendNumber(value); }
protected virtual void AppendDouble(Double value) { AppendNumber(value); }
protected virtual void AppendSingle(Single value) { AppendNumber(value); }
複製程式碼

這樣的好處是我可以在子類中靈活的選擇重寫全部的數字型別,還是隻重寫某種特定型別

然後接著,我需要完成一些已知型別的轉換方法,比如陣列,集合,字典,資料表等等

protected virtual void AppendArray(IEnumerable array)//陣列,集合
protected virtual void AppendJson(IDictionary dict)//字典
protected virtual void AppendDataSet(DataSet dataset)//資料表集
protected virtual void AppendDataTable(DataTable table)//單表
protected virtual void AppendDataView(DataView view)//表檢視

 

 ps:這些方法,單個實現都不困難,限於篇幅問題,這裡就不介紹了,最後會放出完整程式碼

已知型別處理完後,再新增一個對未知型別的處理方法,這裡我用的是最基本的反射

複製程式碼
protected virtual void AppendOther(object obj)
{
    Type t = obj.GetType();
    Buff.Append('{');
    string fix = "";
    foreach (var p in t.GetProperties())
    {
        if (p.CanRead)
        {
Buff.Append(fix); AppendKey(p.Name, false); object value = p.GetValue(obj, null); AppendObject(value); fix = ","; } } Buff.Append('}'); }
複製程式碼

 

 

實際上有2個方法是目前為止不存在的

現在我們把他加上

複製程式碼
/// <summary> 追加Key
/// </summary>
/// <param name="key"></param>
/// <param name="escape">key中是否有(引號,回車,製表符等)特殊字元,需要轉義</param>
protected virtual void AppendKey(string key, bool escape)
{
    if (escape)
    {
        AppendString(key);
    }
    else
    {
        Buff.Append('"');
        Buff.Append(key);
        Buff.Append('"');
    }
    Buff.Append(':');
}
private Dictionary<object, object> _LoopObject = new Dictionary<object, object>();//迴圈引用物件快取區
//泛物件
protected void AppendObject(object obj)
{
    if (obj == null) Buff.Append("null");else if (obj is String) AppendString((String)obj);
    else if (obj is Int32) AppendInt32((Int32)obj);
    else if (obj is Boolean) AppendBoolean((Boolean)obj);
    else if (obj is DateTime) AppendDateTime((DateTime)obj);
    else if (obj is Double) AppendDouble((Double)obj);
    else if (obj is Enum) AppendEnum((Enum)obj);
    else if (obj is Decimal) AppendDecimal((Decimal)obj)  ;
    else if (obj is Char) AppendChar((Char)obj);
    else if (obj is Single) AppendSingle((Single)obj);
    else if (obj is Guid) AppendGuid((Guid)obj);
    else if (obj is Byte) AppendByte((Byte)obj);
    else if (obj is Int16) AppendInt16((Int16)obj);
    else if (obj is Int64) AppendInt64((Int64)obj);
    else if (obj is SByte) AppendSByte((SByte)obj);
    else if (obj is UInt32) AppendUInt32((UInt32)obj);
    else if (obj is UInt64) AppendUInt64((UInt64)obj);
    else if (_LoopObject.ContainsKey(obj) == false)
    {
        _LoopObject.Add(obj, null);
        if (obj is IDictionary) AppendJson((IDictionary)obj);
        else if (obj is IEnumerable) AppendArray((IEnumerable)obj);
else if (obj is DataSet) AppendDataSet((DataSet)obj);
else if (obj is DataTable) AppendDataTable((DataTable)obj);
else if (obj is DataView) AppendDataView((DataView)obj); else AppendOther(obj); _LoopObject.Remove(obj); } else { Buff.Append("undefined"); } }
複製程式碼

 這2個方法都比較好理解,

一個是用來處理Key的,這裡預留了一個引數escape,是為了效能的一些考慮,比如反射時的屬性名稱,這個是絕對不可能出現一些特殊符號的,所以可以直接作為Json的Key使用

另一個方法是用來作為泛物件(不是泛型物件)的入口方法,所有物件通過這個方法都可以找到對應的處理方法

還有一個物件_LoopObject,這個物件是為了解決迴圈引用的問題的,比如常用的物件Page(當然沒有人會把這個物件轉為Json,這裡只是用來做說明)中就有一個Page的屬性,指向this,如果沒有額外的處理,解析將會進入一個迴圈遞迴,直到棧溢位(目前已知的幾個第三方元件對這個情況的支援都不好,都會丟擲異常,這個以後的文章會詳細說明),而我這裡做的處理是將這種無法解析迴圈引用物件都返回undefined,也正好可以區別於空物件的null

 完整程式碼

  JsonBuilder

完整程式碼中有部分程式碼做了調整

呼叫部分

測試下json格式

 

靈活擴充套件

例如 上面的栗子中,列舉轉出的是他的字串形式

如果現在我需要把所有列舉轉為對應的數字怎麼做呢?

複製程式碼
public class EnumValueJsonBuilder : JsonBuilder
{
    protected override void AppendEnum(Enum value)
    {
        Buff.Append(value.GetHashCode());
    }
}
複製程式碼

 新建一個類,然後過載AppendEnum就可以了

真是超級簡單的啦~~~