Theme NexT works best with JavaScript enabled

ShunNien's Blog

不積跬步,無以致千里;不積小流,無以成江海。

0%

Json.net 的自訂 Serialize 與 Deserialize

Json.net 的自訂序列化與反序列化

會使用 Json.net 此自訂序列化 (Serialize) 或是 反序列化 (Deserialize) 的原因主要是希望在序列化 Json 的時候,可以直接將時間的部分按照客戶要求輸出

先附上使用環境與套件版本

  • Newtonsoft.Json 版本 : 10.0.2
  • 編輯器 LINQPad 版本 : 5.31.00(AnyCPU)

首先建立一個範例 Class 叫做 SampleModel ,然後利用時區讓兩個 DateTime 格式的屬性來做範例輸出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Main()
{
SampleModel model = new SampleModel();

// 格式化輸出
var indented = Newtonsoft.Json.Formatting.Indented;

var jsonModel = JsonConvert.SerializeObject(model, indented);
jsonModel.Dump();
}

/// <summary>
/// Class SampleModel.
/// </summary>
public class SampleModel
{
public int Id { get; set; } = 1;
public DateTime UtcDate { get; set; } = DateTime.UtcNow;
public DateTime NowDate { get; set; } = DateTime.Now;
}

sample class serialize

序列化

承襲上述的 SampleModel ,此部分針對 Json.netJsonConverter 來自訂規則,主要需要撰寫的部分有 CanConvertWriteJson 兩個方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void Main()
{
SampleModel model = new SampleModel();
// 格式化輸出
var indented = Newtonsoft.Json.Formatting.Indented;

// 設定自訂規則轉換 DateTime 格式
var jsonModel = JsonConvert.SerializeObject(model, indented, new SampleModelConverter(typeof(DateTime)));
jsonModel.Dump();
}

// Class SampleModel. 請參照上述,此處省略

/// <summary>
/// Class SampleModelConverter.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public class SampleModelConverter : JsonConverter
{
private readonly Type[] _types;

/// <summary>
/// 建構式
/// </summary>
/// <param name="types">The types.</param>
public SampleModelConverter(params Type[] types)
{
_types = types;
}

/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
if (type == typeof(DateTime))
{
var date = (DateTime)value;
serializer.Serialize(writer, date.ToLocalTime().ToString("yyyy-MM-dd hh:mm:ss"));

// 想寫入 json 物件,使用以下語法
// writer.WriteStartObject();
// writer.WritePropertyName(type.Name);
// writer.WriteEndObject();
}
}

/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return null;
}

/// <summary>
/// 判斷此物件是否符合自訂規則的設定型別,符合即可進行轉換
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns><c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.</returns>
public override bool CanConvert(Type objectType)
{
var result = _types.Any(t => t == objectType);
return result;
}
}

serializer result

Deserialize反序列化

反序列化要注意的是要自訂 ReadJson 這個方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void Main()
{
SampleModel model = new SampleModel();
// 格式化輸出
var indented = Newtonsoft.Json.Formatting.Indented;

// 設定自訂規則轉換 DateTime 格式
var jsonModel = JsonConvert.SerializeObject(model, indented, new SampleModelConverter(typeof(DateTime)));
jsonModel.Dump();

// 設定自訂規則轉換 DateTime 格式
var deserializeJson = JsonConvert.DeserializeObject<SampleModel>(jsonModel, new SampleModelConverter(typeof(DateTime)));
deserializeJson.Dump();

}

// Class SampleModel. 請參照上述,此處省略

/// <summary>
/// Class SampleModelConverter.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public class SampleModelConverter : JsonConverter
{
// ... 其他方法與屬性同序列化的自訂,除 ReadJson 方法不同

/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
// reader Json 讀取出來的類型為 Date 格式就進行轉換
// 當序列化使用此規則時,此處可以省略,因為會沒有 Date 格式
if (reader.TokenType == JsonToken.Date)
{
return ((DateTime)existingValue).ToLocalTime();
}

if (DateTime.TryParse(existingValue.ToString(), out DateTime dateValue))
{
return dateValue.ToLocalTime();
}

return null;
}
}

Deserialization result

總結

Json.netJsonConverter 訂立的規則是通用的,不是針對單一型別的規則,當然此範例還可以使用 *JsonSerializer.DateFormatString * 等各種方式達到目的,不過此範例重點在於自訂規則與轉換型別的部分,最後附上完整的 sample code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
void Main()
{
SampleModel model = new SampleModel();

// 格式化輸出
var indented = Newtonsoft.Json.Formatting.Indented;

var jsonModel = JsonConvert.SerializeObject(model, indented, new SampleModelConverter(typeof(DateTime)));
jsonModel.Dump();

var deserializeJson = JsonConvert.DeserializeObject<SampleModel>(jsonModel, new SampleModelConverter(typeof(DateTime)));
deserializeJson.Dump();
}

/// <summary>
/// Class SampleModel.
/// </summary>
public class SampleModel
{
public int Id { get; set; } = 1;
public DateTime UtcDate { get; set; } = DateTime.UtcNow;
public DateTime NowDate { get; set; } = DateTime.Now;
}

/// <summary>
/// Class SampleModelConverter.
/// </summary>
/// <seealso cref="Newtonsoft.Json.JsonConverter" />
public class SampleModelConverter : JsonConverter
{
private readonly Type[] _types;

/// <summary>
/// Initializes a new instance of the <see cref="SampleModelConverter"/> class.
/// </summary>
/// <param name="types">The types.</param>
public SampleModelConverter(params Type[] types)
{
_types = types;
}

/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter" /> to write to.</param>
/// <param name="value">The value.</param>
/// <param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
Type type = value.GetType();
if (type == typeof(DateTime))
{
var date = (DateTime)value;
serializer.Serialize(writer, date.ToLocalTime().ToString("yyyy-MM-dd hh:mm:ss"));
}
}

/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader" /> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>The object value.</returns>
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Date)
{
return ((DateTime)existingValue).ToLocalTime();
}
if (DateTime.TryParse(existingValue.ToString(), out DateTime dateValue))
{
return dateValue.ToLocalTime();
}
return null;
}

/// <summary>
/// 判斷此物件是否符合自訂規則的設定型別,符合即可進行轉換
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns><c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.</returns>
public override bool CanConvert(Type objectType)
{
var result = _types.Any(t => t == objectType);
return result;
}
}

延伸閱讀

文中的時區轉換都比較偷懶,都是使用 ToLocalTime 等方式處理,要執行轉換的話,建議可以使用 TimeZoneInfo 來轉換,如下範例

1
2
3
4
5
6
7
var twtzinfo = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");
DateTime localdt = TimeZoneInfo.ConvertTime(DateTime.UtcNow,TimeZoneInfo.Utc, twtzinfo);
DateTime localdt2 = TimeZoneInfo.ConvertTime(DateTime.UtcNow, twtzinfo);
DateTime localdt3 = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, twtzinfo);
localdt.Dump();
localdt2.Dump();
localdt3.Dump();

附上拉取時區名稱與時區 ID 的作法,或是參考 microsoft 時區 ID 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TimeZoneInfo localZone = TimeZoneInfo.Local;

Dictionary<string, string> timeZoneIdName = new Dictionary<string, string>()
{
{ localZone.Id, localZone.DisplayName}
};

timeZoneIdName.Dump();
ReadOnlyCollection<TimeZoneInfo> list = TimeZoneInfo.GetSystemTimeZones();
foreach (var zone in list)
{
if (!timeZoneIdName.ContainsKey(zone.Id))
timeZoneIdName.Add(zone.Id, zone.DisplayName);
}

timeZoneIdName.Dump();

歡迎關注我的其它發布渠道