_์ด๋ฏธ ๋ค๋ฅธ ๊ณณ์์ ๋ต๋ณ์ด ๋์๋ค๋ฉด ์ฃ์กํฉ๋๋ค๋ง, ๊ฒ์ ๋ฅ๋ ฅ์ด ๋จ์ด์ก๋ค๋ฉด ์ฃ์กํฉ๋๋ค._
๊ธฐ์กด ํ๋ก๋์
์ ํ๋ฆฌ์ผ์ด์
์ฝ๋๋ก 3.0์ ํ๋ฆฌ๋ทฐ 6์ ์ํํด๋ณด๊ณ ์๋๋ฐ ํ
์คํธ์์ ๋์จ ๋ฌธ์ ์ค ํ๋๋ API ๊ณ์ฝ์์ ์ฌ์ฉํ๋ ๊ธฐ์กด ๊ฐ์ฒด ์ค ์ผ๋ถ๊ฐ TimeSpan
์์ฑ์ ์ฌ์ฉํ๋ค๋ ๊ฒ์
๋๋ค. ๊ฐ์ ๋ฌธ์์ด๋ก ํ์๋ฉ๋๋ค.
์๋ก์ด System.Text.Json API์ ๋ํด TimeSpan
์์ฑ์ ๋ํ ์ง์์ด 3.0์ ๊ณํ๋์ด ์์ต๋๊น?
9์ ์ด์ ์ ๋ฆฌํฉํ ๋ง์ ์ํํ์ฌ ๋ฌธ์์ด๋ก ๋ณ๊ฒฝํ์ฌ ์ ์ง๋ ฌ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ ์ ์๋๋ก ์๋ฆผ์ ์ ๊ณตํ์ง ์๋ ๊ฒฝ์ฐ ๊ณํ๋์์ง๋ง ์์ง ๊ตฌํ๋์ง ์์ ๊ฒ์ฒ๋ผ ๋์ค์ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ๋ฅผ ๊ธฐ๋ค๋ ค์ผ ํฉ๋๋ค. ์ด ์์ ์ ์ํํฉ๋๋ค.
๋ค์์ ๊ธฐ์กด JSON .NET ์ฝ๋์ ๋น๊ตํ์ฌ TimeSpan
์ฒ๋ฆฌ ์คํจ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ต์ ์ฌํ ๋จ์ ํ
์คํธ์
๋๋ค.
using System;
using System.Text.Json.Serialization;
using Xunit;
using JsonConvert = Newtonsoft.Json.JsonConvert;
using JsonSerializer = System.Text.Json.Serialization.JsonSerializer;
namespace JsonSerializerTimeSpanNotSupportedException
{
public static class Repro
{
[Fact]
public static void Can_Deserialize_Object_With_SystemTextJson()
{
// Arrange
string json = "{\"child\":{\"value\":\"00:10:00\"}}";
// Act (fails in preview 6, throws: System.Text.Json.JsonException : The JSON value could not be converted to System.TimeSpan. Path: $.child.value | LineNumber: 0 | BytePositionInLine: 28.)
var actual = JsonSerializer.Parse<Parent>(json, new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
// Assert
Assert.NotNull(actual);
Assert.NotNull(actual.Child);
Assert.Equal(TimeSpan.FromMinutes(10), actual.Child.Value);
}
[Fact]
public static void Can_Deserialize_Object_With_NewtonsoftJson()
{
// Arrange
string json = "{\"child\":{\"value\":\"00:10:00\"}}";
var actual = JsonConvert.DeserializeObject<Parent>(json);
// Assert
Assert.NotNull(actual);
Assert.NotNull(actual.Child);
Assert.Equal(TimeSpan.FromMinutes(10), actual.Child.Value);
}
private sealed class Parent
{
public Child Child { get; set; }
}
private sealed class Child
{
public TimeSpan Value { get; set; }
}
}
}
์ฐ๋ฆฌ๋ ํ์ฌ TimeSpan
๋ฅผ ์ง์ํ ๊ณํ์ด ์์ผ๋ฉฐ ์์ผ๋ก ์ถ๊ฐ๋ ์์ ์ด์ง๋ง ์ผ๋ง๋ ๋ง์ ์์
์ด ๊ด๋ จ๋์ด ์๋์ง ์์๋ณด๊ธฐ ์ํด ์ฝ๊ฐ์ ์กฐ์ฌ๋ฅผ ํ ์ ์์ต๋๋ค. ๋๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ผ๋ก TimeSpan
๋ฅผ ์ง์ํ๊ธฐ ์ํด ์์ฒด JsonConverter
๋ฅผ ์์ฑํ ์ ์์ต๋๋ค. ๋ค์์ฃผ๋ง๊น์ง ์
๋ฐ์ดํธ ํ๊ฒ ์ต๋๋ค. ๊ฐ์ฌ ํด์.
์ถ๊ฐํ๋ค๋ฉด api ๊ฒํ ๋ฅผ ๊ฑฐ์ณ์ผ ํ๋ reader/writer/JsonElement์ TimeSpan
API๋ ์ถ๊ฐํ๊ณ ์ถ์ ๊ฒ์
๋๋ค.
๊ฐ์ฌํฉ๋๋ค. ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ฉด 3.0์ฉ ์ฑ์์ ์๋ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๊ธฐ๋ฅ์ด ๋ฏธ๋ฆฌ ๋ณด๊ธฐ 7๊ณผ ํจ๊ป ์ ๊ณต๋ ์์ ์ ๋๊น?
์, ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ ์ง์์ ์ด์ ๋ง์คํฐ์ ์์ผ๋ฏ๋ก ๋ฏธ๋ฆฌ ๋ณด๊ธฐ 7์ ์์ ๊ฒ์ ๋๋ค.
๊ทธ๋ฌ๋ TimeSpan์ ์ผ๋ฐ์ ์ธ BCL ์ ํ์ด๋ฏ๋ก ์ฌ์ ํ ๊ธฐ๋ณธ ๋ณํ๊ธฐ๋ฅผ ์ ๊ณตํด์ผ ํฉ๋๋ค.
์ค๋ ๊ฒํ ํ์ต๋๋ค.
- ์๊ฐ ๋ฒ์์ ๋ํ ํ์ค ์ธ์ฝ๋ฉ์ด ์๋์ง ์ฌ๋ถ๋ ๋ถ๋ถ๋ช ํฉ๋๋ค.
์๊ฐ ๋ฒ์์ ๋ํ ํ์ค ์ธ์ฝ๋ฉ์ ISO8601์ "duration" ์
๋๋ค. ์ด๊ฒ์ XML ์ด ๊ฒ์ด๊ธฐ ๋ ํ๋ฉฐ XmlConvert
๋ฐ XsdDuration
์ด๋ฏธ ๊ตฌํ๋์ด ์์ต๋๋ค.
์ด์ TimeSpan
์์ฒด๊ฐ ISO8601 ๊ธฐ๊ฐ ํ์์ ๊ตฌ๋ฌธ ๋ถ์ ๋ฐ ํ์ ์ง์ ์ ์ง์ํด์ผ ํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ๋จ๊ณ๋ก DateTime[Offset]
์ ์๋ณต("O", "o") ํ์ ์ง์ ์์ ์ ์ฌํ ์๋ก์ด ํ์ค TimeSpan
ํ์ ๋ฌธ์์ด์ ์ถ๊ฐํ๋ ๊ฒ์ด ๋ ๋์๊น์? ๊ทธ ํ์ ๋ณํ๊ธฐ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ์ ๋ง ๊ฐ๋จํฉ๋๋ค.
์ด๊ฒ์ ๋ํ AspNetCore๊ฐ API์์ TimeSpan
์ ํ์ ๋ฐํํ๋ ๋ฐฉ์์ ๊ธฐ๋ณธ ๋์์ ๋ณ๊ฒฝํ์ต๋๋ค. dotnet/AspnetCore#11724๋ฅผ ์ฐธ์กฐํ์ธ์. ์ด๊ฒ์ ํ๊ธฐ์ ์ธ ๋ณํ์ผ ์ ์์ต๋๋ค.
๊ฐ์ฅ ๊ฐ๋จํ ์๋ฃจ์ ์ ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ๋ฅผ ๋ง๋๋ ๊ฒ์ ๋๋ค.
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
๊ณ ๊ฐ ์ฆ๊ฑฐ์ ๋ํด ์ด์ผ๊ธฐํ๊ธฐ: TimeSpan์ ๋ํ ์ํํธ์จ์ด ๊ฐ๋ฐ ์ง์์ด ๋งค์ฐ ํ์ํฉ๋๋ค.
์ค๋ .NET Core 3.0์ผ๋ก ์ฑ์ ํฌํ ํ๋ ์ผ์ ๋ถ๋ช์ณค์ต๋๋ค. ์ด๊ฒ์ด ๋ซํ ์๊ธฐ ๋๋ฌธ์ Microsoft๊ฐ ๊ธฐ๋ณธ ์ง์์ ์ถ๊ฐํ ๊ณํ์ด ์๋ค๋ ์๋ฏธ์ ๋๊น? @khellang ์ ์๊ฒฌ์ ์ด๋๊ฐ์ ๋ก๋๋งต์ ์์ด์ผ ํ๋ค๋ ๊ฝค ์ค๋๋ ฅ ์๋ ์ฃผ์ฅ์ธ ๊ฒ ๊ฐ์ต๋๋ค...
์ถ๊ฐ ์์ฒญ์ ๋ฐ๋ผ 5.0์ผ๋ก ๋ค์ ์ฝ๋๋ค. Newtonsoft์์ ํธํ์ฑ๋ ๊ณ ๋ คํด์ผ ํ์ง๋ง ISO8601 ๊ธฐ๊ฐ์ด ๊ฐ์ฅ ์ข์ ํํ์ผ ๊ฒ์ ๋๋ค.
์ค๋์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๊ธฐ๋ณธ ๋์์ ์ด์ ๋์๋ณด๋ค ๋์ผ๋ฉฐ ์์ ํ ์์์น ๋ชปํ ๊ฒ์ด์์ต๋๋ค. ISO8601์ ์ฌ์ฉํ๊ฑฐ๋ Newtonsoft์ ํธํ๋๋๋ก ์์ ํด์ผ ํฉ๋๋ค.
๊ธฐ๋ณธ ๋์์ ์ด์ ๋์๋ณด๋ค ๋์ผ๋ฉฐ ์์ ํ ์์์น ๋ชปํ ๊ฒ์ด์์ต๋๋ค.
@mfeingol ๋ฌด์จ ํ๋? ๊ทธ๊ฒ์ ๋จ์ํ ์คํจ?
ISO8601์ ์ฌ์ฉํ๊ฑฐ๋ Newtonsoft์ ํธํ๋๋๋ก ์์ ํด์ผ ํฉ๋๋ค.
@rezabayesteh๊ฐ ์ธ๊ธํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ ๋ง ์ฝ์ต๋๋ค.
@khellang : ์๋์ ์ผ๋ก Timespan?
๋ฅผ HasValue
ํ๋๋ก ์ง๋ ฌํํ ๋ค์ TimeSpan ๊ตฌ์กฐ์ฒด์ ๊ฐ ์์ฑ์ ์ง๋ ฌํํ๋ค๋ ๊ฒ์
๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ ๋ง ์ฝ์ต๋๋ค.
๊ทธ๋ ๊ฒ ํ์ง๋ง ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์ ํ์๋ ํ์ํ์ง ์์ต๋๋ค.
์ค๋(๋ด ๊ณ ๊ฐ์ด ๋ณด๊ณ ํ) ์ด ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ์ผ๋ฉฐ Startup.cs์ ๊ตฌ์ฑ์ ์ฌ์ฉํ๋ ๋์ Newtonsoft.Json ์ง๋ ฌ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ชจ๋ aspnet webapi ๋ฐ ์ฑ์ ๋ค์ ์ ํํด์ผ ํฉ๋๋ค.
์๋น์ค.์ถ๊ฐ ์ปจํธ๋กค๋ฌ()
.AddNewtonsoftJson(์ต์
=>
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});
์ ๊ฒฝ์ฐ์๋ ๋ช ๊ฐ์ง nullable TimeSpan(TimeSpan?)์ ์ฌ์ฉํ๊ณ System.Text.Json์ ์ด๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์ง๋ ฌํํ์ต๋๋ค.
{
"hasValue": ์ฐธ,
"๊ฐ": {
"ํฑ":0,
"์ผ": 0,
"์๊ฐ": 0,
"๋ฐ๋ฆฌ์ด": 0,
"๋ถ": 0,
"์ด": 0,
"์ด ์ผ์": 0,
"์ด ์๊ฐ": 0,
"์ด ๋ฐ๋ฆฌ์ด": 0,
"์ด ์๊ฐ": 0,
"์ด์ด": 0
}
}
์ด๊ฒ์ ์น ๋ธ๋ผ์ฐ์ ์ ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ์ฒด์ ๋ด API๋ฅผ ์ฌ์ฉํ๋ ๋ค์ํ ํ๋ซํผ ๊ฐ(๋ค์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ฅผ ์๋ฏธ) ์ญ์ง๋ ฌ ๋ณํ๊ธฐ์ ์ฝ๊ฐ์ ๋ฌธ์ ๋ฅผ ์ผ์ผํต๋๋ค.
Newtonsoft.Json ์ง๋ ฌ ๋ณํ๊ธฐ์ ๋์ผํ ์ง๋ ฌํ ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋ํฉ๋๋ค.
{
"์๊ฐ ๋ฒ์": "00:00:00.0000000",
"nullTimeSpan": null
}
์ค๋(๋ด ๊ณ ๊ฐ์ด ๋ณด๊ณ ํ) ์ด ๋ฌธ์ ๋ฅผ ๋ฐ๊ฒฌํ์ผ๋ฉฐ ๋์ Newtonsoft.Json ์ง๋ ฌ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋ชจ๋ aspnet webapi ๋ฐ ์ฑ์ ๋ค์ ์ ํํด์ผ ํฉ๋๋ค.
@bashocz https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476์ ์ธ๊ธ๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋ํ์ง ์๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
#38641(๋๊ธ)์ ์ธ๊ธ๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋ํ์ง ์๋ ์ด์ ๋ ๋ฌด์์ ๋๊น?
@khellang ์ ๋ TimeSpan ์ง๋ ฌํ๊ฐ ๋ค๋ฅธ ๊ฐ๋ฐ์์ ๋ฌธ์ ์์ ๊ฐ์กฐํ๊ณ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ด๊ณ ์ถ์ต๋๋ค. System.Text.Json์ผ๋ก ์ ํํ๊ณ ์ถ์ง๋ง ๋ช ๊ฐ์ง ์ฅ์ ๋ฌผ์ด ์์ต๋๋ค.
๋ช ์ฃผ ์ ์ ์๋ก์ด System.Text.Json์ ์กฐ์ฌํ๋๋ฐ ๊ธฐ๋ฅ์ด ์์ ํ์ง ์๋ค๋ ๊ฒ์ ์์์ต๋๋ค. dotnet/corefx#41747 ๋ฌธ์ ๋ฅผ ์ ๊ธฐํ์ผ๋ฉฐ ๋ค๋ฅธ dotnet/corefx#39031, dotnet/corefx#41325, dotnet/corefx#38650 ๊ด๋ จ ๋ฌธ์ ๋ฅผ ์ง์ ํ์ต๋๋ค. ๊ทธ ๋๋ฌธ์ ์ฐ๋ฆฌ์ ๋ชจ๋ ๋ด๋ถ ๋ง์ดํฌ๋ก์๋น์ค๋ ์ฌ์ ํ Newtonsoft.Json์ ์ฌ์ฉํฉ๋๋ค.
์ ์ ์๋ ์ด์ ๋ก ๊ณต๊ฐ API์ ์น์ฑ์์๋ ์ด๋ฅผ ์์ ํ๊ธฐ ์ํด ๊ฐ๋ฐ์๋ฅผ ๊ด๋ฆฌํ๋ ๊ฒ์ ์์์ต๋๋ค.
BTW: ํ๋ก๋์ ์ฝ๋์์ ๊ฐ๋ฅํ ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ํผํ๋ ค๊ณ ํฉ๋๋ค. ํฅํ ์ ์ง ๊ด๋ฆฌํ๊ณ ์ ๊ฑฐํ๊ธฐ๊ฐ ์ด๋ ต์ต๋๋ค.
@khellang , ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋ํ์ง ์๋ ๊ฒ์ ์๋๋๋ค. ๊ฐ๋ฐ์๊ฐ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ถ๊ฐํ ํ์๊ฐ ์๋ ๊ธฐ๋ณธ์ ์ธ ๊ฒ์ ๋๋ค. .NET Core 3์ ๋์ ๋ ํฐ ๊ธฐ๋ฅ์ผ๋ก์ ์ด๋ฌํ ๊ธฐ๋ณธ ๊ตฌํ์ด ๋ถ์กฑํด์๋ ์ ๋ฉ๋๋ค.
@arisewanggithub ์ปจํธ๋กค๋ฌ์ ์ฌ์ฉํ ์ ์๋ ์ ์ญ ์ค์ ์ด ์์ต๋๋ค. AddJsonOptions()
๋ฅผ ํตํด ๊ตฌ์ฑํ๊ฑฐ๋ JsonSerializerOptions
์ธ์คํด์ค๋ฅผ Json()
์ปจํธ๋กค๋ฌ ๋ฉ์๋์ ์ ๋ฌํ ์ ์์ต๋๋ค.
์ด๊ฒ์ด ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋ ํ์๋ ์์ธ์ ๋๊น?!
์ด๊ฒ์ด ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์๋ ํ์๋ ์์ธ์ ๋๊น?!
API/๋์ ์ ์, ๊ฒํ ๋ฐ ๊ตฌํ์ด ๋ณด๋ฅ ์ค์ธ ๋ฌธ์ ๋ ์์ง ํด๊ฒฐ๋์ง ์์์ต๋๋ค. ๊ทธ๋์ https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476์ ์ธ๊ธ๋ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์ ๋ฅผ ์ฝ๊ฒ ํด๊ฒฐํ ์ ์์ต๋๋ค.
์ด๊ฒ์ผ๋ก ์ฐจ๋จ๋ ์ฌ๋์ ์ํด 5.0 ๋๋กญ ์ ์ ์ฌ์ฉํ ์ ์๋ ๋ณํ๊ธฐ(JsonTimeSpanConverter)๊ฐ ํฌํจ๋ NuGet ํจํค์ง๊ฐ ์์ต๋๋ค. Macross.Json.Extensions
TimeSpan ๋ฐ Nullable<TimeSpan> ์ ํ์ ์ง์ํฉ๋๋ค.
์ ์ฅ, ์ํ!
์ด๊ฒ์ผ๋ก ์ฐจ๋จ๋ ์ฌ๋์ ์ํด 5.0 ๋๋กญ ์ ์ ์ฌ์ฉํ ์ ์๋ ๋ณํ๊ธฐ(JsonTimeSpanConverter)๊ฐ ํฌํจ๋ NuGet ํจํค์ง๊ฐ ์์ต๋๋ค. Macross.Json.Extensions
TimeSpan ๋ฐ Nullable ์ง์
์ ํ.
๋ด ํ ์คํธ์ ๋ฐ๋ฅด๋ฉด nullable ๊ฐ ์ ํ์ ์ด๋ฏธ ํ๋ ์์ํฌ์์ ์ฒ๋ฆฌ๋ฉ๋๋ค. ๋ค์ ์ด์์ ํ์ํ์ง ์์ต๋๋ค.
public class DelegatedStringJsonConverter<T> : JsonConverter<T>
where T : notnull
{
private static readonly bool s_typeAllowsNull = !typeof(T).IsValueType || Nullable.GetUnderlyingType(typeof(T)) != null;
private readonly Func<string, T> _parse;
private readonly Func<T, string> _toString;
public DelegatedStringJsonConverter(Func<string, T> parse, Func<T, string> toString)
{
_parse = parse;
_toString = toString;
}
public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
// null tokens are handled by the framework except when the expected type is a non-nullable value type
// https://github.com/dotnet/corefx/blob/v3.1.4/src/System.Text.Json/src/System/Text/Json/Serialization/JsonSerializer.Read.HandleNull.cs#L58
if (!s_typeAllowsNull && reader.TokenType == JsonTokenType.Null)
throw new JsonException($"{typeof(T)} does not accept null values.");
return _parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
{
// value is presumably not null here as null values are handled by the framework
writer.WriteStringValue(_toString(value));
}
}
(ํธ์ง: ํ๋ ์์ํฌ ์์ค๋ฅผ ์์ธํ ์ดํด๋ณธ ํ null ํ ํฐ/null ๊ฐ์ ๋ํ ๊ฒ์ฌ๊ฐ ์ค๋ณต๋๋ ๊ฒ์ผ๋ก ๋ํ๋ฌ์ต๋๋ค. ์ด์ ๋ฐ๋ผ ์์ ์ฝ๋๊ฐ ์ ๋ฐ์ดํธ๋์์ต๋๋ค.)
๊ทธ๋ฐ ๋ค์ JsonSerializerOptions
๊ณผ ๊ฐ์ด ๊ตฌ์ฑํฉ๋๋ค.
options.Converters.Add(new DelegatedStringJsonConverter<TimeSpan>(
value => TimeSpan.Parse(value, CultureInfo.InvariantCulture),
value => value.ToString(null, CultureInfo.InvariantCulture)));
๋ช ์ค์ ์ถ๊ฐ ์ฝ๋์ ๋์ฒํ ์ ์๋ค๋ฉด ํ์ฌ ์ข ์์ฑ์ ์ถ๊ฐํ๋ ๊ฒ์ ์ข์ ์๊ฐ์ด ์๋๋ผ๊ณ ์๊ฐํฉ๋๋ค. ์ฐ๋ฆฌ๋ ๊ฒฐ๊ตญ NPM ๋ ์ ์์ง ์์ต๋๋ค. ;)
๋ฌผ๋ก ๊ทธ๋ฌํ ๊ธฐ๋ณธ ์ ํ์ ๋ํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ด ์ ํ ํ์ํ์ง ์๋ค๋ฉด ๊ฐ์ฅ ์ข์ต๋๋ค.
@adams85 ์ฐธ๊ณ ๋ก Nullable ๊ฐ ์ ํ์ JsonConverterAttribute์ ํจ๊ป ์ฌ์ฉํ ๋ < .NET 5๋ก ๋ฒ๊ทธ๊ฐ ์์ต๋๋ค. #32006์ ์ฐธ์กฐํ์ญ์์ค. ์ ๋ ๊ฐ์ธ์ ์ผ๋ก ์ ์ญ ๊ตฌ์ฑ๋ณด๋ค ์์ฑ ์คํ์ผ์ ์ฌ์ฉํ๋ ๊ฒ์ ์ ํธํ๋ฏ๋ก ํ์ฅ ๋ฐ ๋ฒ๊ทธ ์์ ์ด ๊ฐ๋ฅํ์ง๋ง ๋ณํ๊ธฐ๋ ํ๋ฅญํฉ๋๋ค. Macros.Json.Extensions์ ์ถ๊ฐํ์ฌ ํํ์ ๋ฐ์ ์ ์๊ณ ๋๋๋ก "NPM ๋ "์ผ๋ก ์ฌํํ๋ ๊ฒ์ ๊บผ๋ฆฌ์ง ์๋ ๋ค๋ฅธ ์ฌ๋์ ๋์๋ ๋ ๊น์? :)
@CodeBlanch JsonConverterAttribute
๋ฅผ ์ง์ ํด ์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค. ๊ทธ๊ฒ์ ํ์คํ ์ ์ฒด ์ด์ผ๊ธฐ์ ์ผ๋ถ์
๋๋ค.
๊ทธ๋ฆฌ๊ณ ๋ฌผ๋ก , ๋น์ ์ด ์ํ๋ค๋ฉด ๋ด ๋ณํ๊ธฐ๋ฅผ ๋น์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. :)
.NET 5.0 Preview 5๋ถํฐ System.Text.Json์ ์ฌ์ฉํ์ฌ ์ํฐํฐ๋ฅผ JSON์ผ๋ก ์๋ณต Cannot skip tokens on partial JSON
์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ๋ฌธ์ ๊ฐ ๋๋ ์ค๊ณผ ๋ฌธ์๋ "Ticks":
์ ์ฝ๋ก ์
๋๋ค.
์ด์จ๋ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฌ์ฉ TimeSpan
๋ฅผ ์ง๋ ฌํํ๋ฉด ์ ๋๋ก ์๋ํฉ๋๋ค.
.json
ํ์ผ์ ์ด์์ ๋์ ์ด๊ธฐ ๋ฐ์์ด ๊ตฌ์กฐ์ ์๊ด์ผ๋ก ์ง๋ ฌํ๋ TimeSpan
๋ฅผ ์ฐพ๊ธฐ ์ํด ๊ฒ์ฌํ์ ๋ "ํ์คํ..."๊ฐ ์๋์๊ธฐ ๋๋ฌธ์
๋๋ค.
https://github.com/dotnet/runtime/issues/42356์ @jsedlak ์์:
๋ฌธ์ ์ ๋ชฉ
System.Text.Json.JsonSerializer ํด๋์ค๋ TimeSpan ์์ฑ์ ์ง๋ ฌํํ ์ ์์ง๋ง ์ญ์ง๋ ฌํํ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ธ
์ด ๋ฌธ์ ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ํ ํ๋ก์ ํธ๋ https://github.com/jsedlak/TestTimeSpan์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
WebApi์์ TimeSpan ์์ฑ์ ๋ฐํํ๋ฉด JSON์ผ๋ก ์ฌ๋ฐ๋ฅด๊ฒ ์ง๋ ฌํ๋ฉ๋๋ค.
[ { "forecastLength": { "ticks": 36000000000, "days": 0, "hours": 1, "milliseconds": 0, "minutes": 0, "seconds": 0, "totalDays": 0.041666666666666664, "totalHours": 1, "totalMilliseconds": 3600000, "totalMinutes": 60, "totalSeconds": 3600 } } ]
๊ทธ๋ฌ๋ ๊ธฐ๋ณธ deserializer ํ์ฅ(
HttpClient.GetFromJsonAsync
)์ ์์ฑ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. ๋น TimeSpan์ ๋ฐํํฉ๋๋ค. ๊ฐ์ฒด๋ฅผ ์ญ์ง๋ ฌํํ๋ ค๋ฉด ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค.๋ท๋ท ์ ๋ณด
`.NET Core SDK(๋ชจ๋ global.json ๋ฐ์):
๋ฒ์ : 3.1.302
์ปค๋ฐ: 41faccf259๋ฐํ์ ํ๊ฒฝ:
OS ์ด๋ฆ: Windows
OS ๋ฒ์ : 10.0.20201
OS ํ๋ซํผ: ์๋์ฐ
RID: win10-x64
๊ธฐ๋ณธ ๊ฒฝ๋ก: C:\Program Files\dotnet\sdk3.1.302\ํธ์คํธ(์ง์์ ์ ์ฉ):
๋ฒ์ : 3.1.6
์ปค๋ฐ: 3acd9b0cd1์ค์น๋ .NET Core SDK:
3.1.302 [C:\Program Files\dotnet\sdk]์ค์น๋ .NET Core ๋ฐํ์:
Microsoft.AspNetCore.All 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]`
.NET 6.0์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ฌ๊ธฐ์์ ์ปค๋ฎค๋ํฐ ๊ธฐ์ฌ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค. ๊ทธ ๋์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ(์: https://github.com/dotnet/runtime/issues/29932#issuecomment -540200476)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค. ๊ตฌํ์ DateTime
๋ฐ DateTimeOffset
๋ํ
.NET 6.0์์ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ณ ์ถ์ต๋๋ค.
.NET 5๋ ์์ง ๋ฆด๋ฆฌ์ค๋์ง ์์์ต๋๋ค. v6์ ๊ธฐ๋ค๋ฆฌ๋ ๋์ v5(semver์์ ์ฃผ์ ์ฃผ์ ๋ณ๊ฒฝ ์ฌํญ v3 => v5 ํ์ฉ)์ ์ด์์ ๊นจ๋ฌผ๋ฉด ์ข๊ฒ ์ต๋๋ค.
์ด ๊ธฐ๋ฅ์ 5.0์ ๋ง์ถ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ๋ ๋๋จธ์ง 5.0 ๊ธฐ๋ฅ ์์ ๊ณผ ํจ๊ป ์ฐ์ ์์๋ฅผ ์ง์ ํด์ผ ํ์ผ๋ฉฐ ์ด
์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ ๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ๋จํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ค์ ๊ฐ์กฐ ํ์ํ๋ ค๋ฉด:
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
var options = new JsonSerializerOptions { Converters = { new TimeSpanConverter() };
JsonSerializer.Serialize(myObj, options);
...
@layomia , ๋๋ ์ด๊ฒ์ 6.0์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
json ๋ณํ๊ธฐ๋ฅผ ์ง์ ๋น๋ํ๋๋ก ์ ํํ ๋ ๋ถ๋ณ ๋ฌธํ๊ถ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return TimeSpan.Parse(reader.GetString(), CultureInfo.InvariantCulture);
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString(format: null, CultureInfo.InvariantCulture));
}
}
์ ๋ asp.net core 3.1๋ก ์์ ํ๊ณ ์๋๋ฐ ์ค๋ ์ด ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. nullable ์๊ฐ ๋ฒ์๊ฐ ํฌํจ๋ ํ์์ ์๊ทธ๋๋ฌ ๋ฐ ์ธ๋ผ์ธ ํธ์ง๊ณผ ํจ๊ป teleriks ๊ทธ๋ฆฌ๋๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ ํ์ด๋ก๋์ ์๊ฐ ๋ฒ์ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋์ด ์์ง๋ง ์ญ์ง๋ ฌํ๊ฐ ์คํจํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๋๋ Macross.Json.Extensions ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ผ๋ถ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ์ฌ์์ ํ์ฌ ์๋ํ๊ฒ ๋ง๋ค์์ต๋๋ค. ์ด์์ ์ด์ง๋ ์์ง๋ง ์ ์ ํ ์์ ์ด ์ด๋ฃจ์ด์ง๊ธธ ๋ฐ๋๋๋ค.
ISO8601 ํ์์ ์ฐพ๊ณ ์๋ค๋ฉด ๋ค์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
```c#
public class TimeSpanConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var stringValue = reader.GetString();
return System.Xml.XmlConvert.ToTimeSpan(stringValue); //8601
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
var stringValue = System.Xml.XmlConvert.ToString(value); //8601
writer.WriteStringValue(stringValue);
}
}
```
์ต์ 24์๊ฐ์ TimeSpan
๋ฅผ ๊ณ ๋ คํ์ญ์์ค. ์: "24:00:00"
๋ new TimeSpan(24, 0, 0)
์ ๊ฐ์์ผ ํฉ๋๋ค.
@gojanpaolo
์ต์ 24์๊ฐ์ TimeSpan์ ๊ณ ๋ คํ์ญ์์ค. ์๋ฅผ ๋ค์ด "24:00:00"์ new TimeSpan(24, 0, 0)๊ณผ ๋์ผํด์ผ ํฉ๋๋ค.
๊ทธ๊ฒ์ ์ค์ ๋ก JSON์ด ์๋๋ฉฐ TimeSpan ๊ตฌ๋ฌธ ๋ถ์ ๋ ผ๋ฆฌ์ ์ผ๋ถ์ ๋๋ค. ์งง์ ๋๋ต: 24์๊ฐ ๋์ "1.00:00:00"์ ์ฌ์ฉํฉ๋๋ค. ๊ธด ๋ต๋ณ: "24:00:00"์ด ์์๋๋ก ๊ตฌ๋ฌธ ๋ถ์๋์ง ์๋ ์ด์ ๋ฅผ ํ์ ํ๊ธฐ ์ํด ๋ฐํ์ ์ฝ๋๋ฅผ ์ ์ ์ดํด๋ณด๊ณ ์ด์ ๋ํ ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ์์ฑํ์ต๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์๊ฐ ๋ฒ์์ ๋ํ ํ์ค ์ธ์ฝ๋ฉ์ ISO8601์ "duration" ์ ๋๋ค. ์ด๊ฒ์ XML ์ด ๊ฒ์ด๊ธฐ ๋ ํ๋ฉฐ
XmlConvert
๋ฐXsdDuration
์ด๋ฏธ ๊ตฌํ๋์ด ์์ต๋๋ค.https://github.com/dotnet/corefx/blob/6d723b8e5ae3129c0a94252292322fc19673478f/src/System.Private.Xml/src/System/Xml/XmlConvert.cs#L1128 -L1146
https://github.com/dotnet/corefx/blob/6d723b8e5ae3129c0a94252292322fc19673478f/src/System.Private.Xml/src/System/Xml/Schema/XsdDuration.cs#L229 -L236
์ด์
TimeSpan
์์ฒด๊ฐ ISO8601 ๊ธฐ๊ฐ ํ์์ ๊ตฌ๋ฌธ ๋ถ์ ๋ฐ ํ์ ์ง์ ์ ์ง์ํด์ผ ํฉ๋๋ค. ์ฒซ ๋ฒ์งธ ๋จ๊ณ๋กDateTime[Offset]
์ ์๋ณต("O", "o") ํ์ ์ง์ ์์ ์ ์ฌํ ์๋ก์ด ํ์คTimeSpan
ํ์ ๋ฌธ์์ด์ ์ถ๊ฐํ๋ ๊ฒ์ด ๋ ๋์๊น์? ๊ทธ ํ์ ๋ณํ๊ธฐ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ์ ๋ง ๊ฐ๋จํฉ๋๋ค.