最近在專案中串接 OpenData 時,遇到了一個問題。某些資料中出現了一些奇怪的符號?
,這在資料類型為 float 時會導致程式出錯。在這篇文章中,我將分享我找到的解決這個問題的方法。
一般 Trim 方法過濾一些符號
參考來源: Trim String in C#
1
2
3
4
5
6
7
8
9
10
|
// String with whitespaces
string hello = " hello C# Corner has white spaces ";
// Remove whitespaces from both ends
Console.WriteLine(hello.Trim());
// String with characters
string someString = ".....My name is Mahesh Chand****";
char[] charsToTrim = { '*', '.' };
string cleanString = someString.Trim(charsToTrim);
Console.WriteLine(cleanString);
|
public string Trim (params char[]? trimChars);
從官方文件中我們可以看到,Trim
方法可以接受多個參數。由於 params
的關係,我們可以直接將要過濾的字符作為參數傳入,如 someString.Trim('*', '.');
。
Trim
方法會從字串的開頭和結尾過濾掉指定的字符。在上面的範例中,Trim
方法過濾掉了變數 someString
開頭的 ‘.’ 字符和結尾的 ‘*’ 字符,得到了乾淨的字串 cleanString
。
更多關於 Trim 方法的資訊,可以參考 。
Json.NET (Newtonsoft) 屬性 Trim 欄位
在處理 JSON 字串的反序列化時,我們可能會遇到一些字串前後有多餘空白的情況。這時,我們可以使用 Json.NET (Newtonsoft) 的 TrimmingConverter
來自動修剪這些空白。以下是一個 TrimmingConverter
的範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class TrimmingConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
return ((string)reader.Value)?.Trim();
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
|
在反序列化過程中,TrimmingConverter
會自動修剪字串的前後空白。以下是一個使用 TrimmingConverter
的範例:
1
2
3
4
|
var json = @"{ name:"" John "" }"
var p = JsonConvert.DeserializeObject<Person>(json, new TrimmingConverter());
Console.WriteLine("Name is: \"{0}\"", p.Name);
//Name is: "John"
|
我們也可以在類別的屬性上使用 JsonConverter
屬性來指定 TrimmingConverter
,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class Person
{
[JsonProperty("name")]
[JsonConverter(typeof(TrimmingConverter))] // <-- that's the important line
public string Name { get; set; }
[JsonProperty("other")]
public string Other { get; set; }
}
var json = @"{ name:"" John "", other:"" blah blah blah "" }"
var p = JsonConvert.DeserializeObject<Person>(json);
Console.WriteLine("Name is: \"{0}\"", p.Name);
Console.WriteLine("Other is: \"{0}\"", p.Other);
//Name is: "John"
//Other is: " blah blah blah "
|
LinqPad範例
更多關於 Json.NET 的 JsonConverter 類別的資訊,可以參考:
System.Text.Json 用屬性 Trim 方法
Warning
我進行的實驗發現,使用 Json.NET 可以正常解析浮點數字串
"122.456"
為
float
,但是使用
System.Text.Json
則會發生錯誤。這顯示
System.Text.Json
在處理數據時會比較嚴謹。在程式上改寫時,我們需要特別注意這一點。ChatGPT 提供了一種解決方案,即使用
JsonConvert
來解決這個問題。
參考:
ChatGPT 問答
Info
這邊我使用了 Json.Net 進行轉換。目前台灣的 OpenData 大部分都是從 csv 轉出的 json,因此大部分的數字都會帶有
"
符號。在這種情況下,使用 Json.Net 進行轉換可能會比較方便。
詳細可以看:
週三、週日廚餘專用限時收受點 | Opendata platform
有時候資料中會包含一些
?
之類的字元,處理起來可能會比較麻煩。
在 Stack Overflow 上也有一篇相關的討論:c# - Deserialize json with auto-trimming strings - Stack Overflow
以下是一個使用 TrimmingConverter
的程式碼範例:
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
|
var json = @"{ ""Name"":"" John "", ""Other"":"" blah blah blah "" }";
var p = JsonSerializer.Deserialize<Person>(json);
Console.WriteLine("name is: \"{0}\"", p.Name);
Console.WriteLine("Other is: \"{0}\"", p.Other);
public class Person
{
[JsonConverter(typeof(TrimmingConverter))] // <-- that's the important line
public string Name { get; set; }
public string Other { get; set; }
}
public class TrimmingConverter : JsonConverter<string>
{
/// <summary>
/// Trim the input string
/// </summary>
/// <param name="reader">reader</param>
/// <param name="typeToConvert">Object type</param>
/// <param name="options">Existing Value</param>
/// <returns></returns>
public override string Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) => reader.GetString()?.Trim();
/// <summary>
/// Trim the output string
/// </summary>
/// <param name="writer">Writer</param>
/// <param name="value">value</param>
/// <param name="options">serializer</param>
public override void Write(
Utf8JsonWriter writer,
string value,
JsonSerializerOptions options) => writer.WriteStringValue(value?.Trim());
}
|
LinqPad範例
在這個程式碼範例中,TrimmingConverter
會在反序列化過程中自動修剪字串的前後空白。這對於處理帶有前後空白的 JSON 字串非常有用。
Info
System.Text.Json 針對 Json 所有屬性做 JsonConverter 也是有的。
1
2
3
4
5
6
7
8
|
var options = new JsonSerializerOptions();
options.Converters.Add(new TrimmingConverter());
var json = @"{ ""Name"":"" John "", ""Other"":"" blah blah blah "" }";
var p = JsonSerializer.Deserialize<Person>(json, options);
Console.WriteLine("name is: \"{0}\"", p.Name);
Console.WriteLine("Other is: \"{0}\"", p.Other);
|
LinqPad範例
我後來選擇 Json.Net 處理過濾特殊字元範例
在處理浮點數字串的轉換時,Json.Net 和 System.Text.Json 的行為有所不同。具體來說,Json.Net 可以自動轉換浮點數字串,而 System.Text.Json 則會報錯。這是由於 System.Text.Json 在處理數字時,對於數字格式的要求比 Json.Net 更嚴格。因此,在這個專案中,我選擇使用 Json.Net 套件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class TrimmingFloatConverter : JsonConverter
{
public override bool CanRead => true;
public override bool CanWrite => false;
public override bool CanConvert(Type objectType) => objectType == typeof(string);
public override object ReadJson(JsonReader reader, Type objectType,
object existingValue, JsonSerializer serializer)
{
return ((string)reader.Value)?.Trim('?', '*', '.', ' ').ToFloat();
}
public override void WriteJson(JsonWriter writer, object value,
JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
|
在上面的程式碼範例中,TrimmingFloatConverter
會在反序列化過程中過濾掉字串中的 ‘?’, ‘*’, ‘.’, ’ ’ 字符,並將過濾後的字串轉換成浮點數。這對於處理帶有特定字符的 JSON 字串非常有用。
Info
我發現 Json.Net 和原生的 System.Text.Json 在處理 JSON 數據時有一些不同,例如在轉換 DateTime 字串格式和將數字字串轉換成數字型別的嚴謹度上。這是我們在選擇使用哪種 JSON 處理庫時需要考慮的因素。
Info
由於 System.Text.Json 的嚴謹度較高,我們無法像在 Json.Net 中那樣直接使用 TrimmingFloatConverter
來過濾和轉換字串。這是我們在使用 System.Text.Json 時需要注意的一點。
Tip
當然 System.Text.Json 也是有對應處理方法,可以參考下面的範例。
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
|
var options = new JsonSerializerOptions();
options.Converters.Add(new FloatStringConverter());
var person = new Person { Name = "John", Height = 1.8f };
var json = @"{""Name"":""John "",""Height"":"" 1.8 ""}";
json.Dump();
var deserializedPerson = JsonSerializer.Deserialize<Person>(json, options);
deserializedPerson.Dump();
public class FloatStringConverter : JsonConverter<float>
{
public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
return float.Parse(value, CultureInfo.InvariantCulture);
}
public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(CultureInfo.InvariantCulture));
}
}
public class Person
{
public string Name { get; set; }
public float Height { get; set; }
}
|
LinqPad範例
彩蛋
Trim strings in JSON paylod
[小菜一碟] Trim () 不只能修剪空白字元而已 | 軟體主廚的程式料理廚房 - 點部落