天天看點

Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

上一篇介紹的 6 個特性從園子裡的回報來看效果不錯,那這一篇就再帶來 6 個特性同大家一起欣賞。

特性分析

1. 像弱類型語言一樣解析 json

大家都知道弱類型的語言有很多,如: nodejs,python,php,它們有一個????????的地方就是處理json,不需要像 強類型語言 那樣還要給它配一個類,什麼意思呢?就拿下面的 json 說事。

{
  "DisplayName": "新一代算法模型",
  "CustomerType": 1,
  "Report": {
    "TotalCustomerCount": 1000,
    "TotalTradeCount": 50
  },
  "CustomerIDHash": [1,2,3,4,5]
}      

這個 json 如果想灌到 C# 中處理,你就得給它定義一個适配的類,就如 初篇 的客戶算法模型類,是以這裡就有了一個需求,能不能不定義類也可以自由解析上面這串 json 呢???哈哈,當然是可以的, 反序列化成 Dictionary 即可,就拿提取 

Report.TotalCustomerCount

 和 

CustomerIDHash

 這兩個字段示範一下。

static void Main(string[] args)
{
    var json = @"{
        'DisplayName': '新一代算法模型',
        'CustomerType': 1,
        'Report': {
            'TotalCustomerCount': 1000,
            'TotalTradeCount': 50
        },
        'CustomerIDHash': [1,2,3,4,5]
        }";

    var dict = JsonConvert.DeserializeObject<Dictionary<object, object>>(json);

    var report = dict["Report"] as JObject;
    var totalCustomerCount = report["TotalCustomerCount"];

    Console.WriteLine($"totalCustomerCount={totalCustomerCount}");

    var arr = dict["CustomerIDHash"] as JArray;
    var list = arr.Select(m => m.Value<int>()).ToList();

    Console.WriteLine($"list={string.Join(",", list)}");
}      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

2. 如何讓json中的枚舉保持更易讀的字元串型

這句話是什麼意思呢?預設情況下, 

SerializeObject

 會将 Model 中的 Enum 變成數值型,大家都知道數值型語義性是非常差的,如下代碼所示:

static void Main(string[] args)
{
    var model = new ThreadModel() { ThreadStateEnum = System.Threading.ThreadState.Running };

    var json = JsonConvert.SerializeObject(model);

    Console.WriteLine(json);
}

class ThreadModel
{
    public System.Threading.ThreadState ThreadStateEnum { get; set; }
}      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

對吧,确實語義特别差,那能不能直接生成 

Running

 這種字元串形式呢?當然可以了。。。改造如下:

var json = JsonConvert.SerializeObject(model, new StringEnumConverter());      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

這裡可能就有人鑽牛角尖了,能不能部分指定讓枚舉生成 string,其他的生成 int ,沒關系,這也難不倒我,哪裡使用就用 

JsonConverter

 标記哪裡。。。

static void Main(string[] args)
{
    var model = new ThreadModel()
    {
        ThreadStateEnum = System.Threading.ThreadState.Running,
        TaskStatusEnum = TaskStatus.RanToCompletion
    };

    var json = JsonConvert.SerializeObject(model);

    Console.WriteLine(json);
}

class ThreadModel
{
    public System.Threading.ThreadState ThreadStateEnum { get; set; }

    [JsonConverter(typeof(StringEnumConverter))]
    public TaskStatus TaskStatusEnum { get; set; }
}      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

3. 格式化 json 中的時間類型

在 model 轉化成 json 的過程中,總少不了 時間類型,為了讓時間類型 可讀性更高,通常會 格式化為 

YYYY年/MM月/dd日

 ,那如何實作呢?很簡單撒,在 JsonConvert 中也是一個 枚舉 幫你搞定。。。

static void Main(string[] args)
{
    var json = JsonConvert.SerializeObject(new Order()
    {
        OrderTitle = "女裝大佬",
        Created = DateTime.Now
    }, new JsonSerializerSettings
    {
        DateFormatString = "yyyy年/MM月/dd日",
    });

    Console.WriteLine(json);
}
public class Order
{
    public string OrderTitle { get; set; }
    public DateTime Created { get; set; }
}      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

對了,我記得很早的時候,C# 自帶了一個 

JavaScriptSerializer

, 也是用來進行 model 轉 json的,但是它會将 datetime 轉成 時間戳,而不是時間字元串形式,如果你因為特殊原因想通過 

JsonConvert

 将時間生成時間戳的話,也是可以的, 用 DateFormatHandling.MicrosoftDateFormat 枚舉指定一下即可,如下:

Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

4. 對一些常用設定進行全局化

在之前所有示範的特性技巧中都是在 

JsonConvert

 上指定的,也就是說 100 個 JsonConvert 我就要指定 100 次,那有沒有類似一次指定,整個程序通用呢?這麼強大的 Newtonsoft 早就支援啦, 就拿上面的 Order 舉例:

JsonConvert.DefaultSettings = () =>
{
    var settings = new JsonSerializerSettings
    {
        Formatting = Formatting.Indented
    };
    return settings;
};

var order = new Order() { OrderTitle = "女裝大佬", Created = DateTime.Now };

var json1 = JsonConvert.SerializeObject(order);
var json2 = JsonConvert.SerializeObject(order);

Console.WriteLine(json1);
Console.WriteLine(json2);      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

可以看到,

Formatting.Indented

 對兩串 json 都生效了。

5. 如何保證 json 到 model 的嚴謹性 及提取 json 未知字段

有時候我們有這樣的需求,一旦 json 中出現 model 未知的字段,有兩種選擇:要麼報錯,要麼提取出未知字段,在 

Newtonsoft

 中預設的情況是忽略,場景大家可以自己找哈。

未知字段報錯

static void Main(string[] args)
        {
            var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備注'}";

            var order = JsonConvert.DeserializeObject<Order>(json, new JsonSerializerSettings
            {
                MissingMemberHandling = MissingMemberHandling.Error
            });

            Console.WriteLine(order);
        }

        public class Order
        {
            public string OrderTitle { get; set; }
            public DateTime Created { get; set; }
            public override string ToString()
            {
                return $"OrderTitle={OrderTitle}, Created={Created}";
            }
        }      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

提取未知字段

我依稀的記得 WCF 在這種場景下也是使用一個 

ExtenstionDataObject

 來存儲用戶端傳過來的未知字段,有可能是用戶端的 model 已更新,server端還是舊版本,通常在 json 序列化中也會遇到這種情況,在 

JsonConvert

 中使用 _additionalData 就可以幫你搞定,在 

OnDeserialized

 這種AOP方法中進行攔截,如下代碼:

static void Main(string[] args)
{
    var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備注'}";

    var order = JsonConvert.DeserializeObject<Order>(json);

    Console.WriteLine(order);
}

public class Order
{
    public string OrderTitle { get; set; }

    public DateTime Created { get; set; }

    [JsonExtensionData]
    private IDictionary<string, JToken> _additionalData;

    public Order()
    {
        _additionalData = new Dictionary<string, JToken>();
    }

    [OnDeserialized]
    private void OnDeserialized(StreamingContext context)
    {
        var dict = _additionalData;
    }

    public override string ToString()
    {
        return $"OrderTitle={OrderTitle}, Created={Created}";
    }
}      
Newtonsoft.Json 六個超簡單又實用的特性,值得一試 【下篇】

6. 開啟 JsonConvert 詳細日志功能

static void Main(string[] args)
{
    var json = "{'OrderTitle':'女裝大佬', 'Created':'2020/6/23','Memo':'訂單備注'}";

    MemoryTraceWriter traceWriter = new MemoryTraceWriter();

    var account = JsonConvert.DeserializeObject<Order>(json, new JsonSerializerSettings
    {
        TraceWriter = traceWriter
    });

    Console.WriteLine(traceWriter.ToString());
}

public class Order
{
    public string OrderTitle { get; set; }

    public DateTime Created { get; set; }

    public override string ToString()
    {
        return $"OrderTitle={OrderTitle}, Created={Created}";
    }
}      

總結