Runtime: 3.0์—์„œ TimeSpan์— ๋Œ€ํ•œ JsonSerializer ์ง€์›?

์— ๋งŒ๋“  2019๋…„ 06์›” 18์ผ  ยท  36์ฝ”๋ฉ˜ํŠธ  ยท  ์ถœ์ฒ˜: dotnet/runtime

_์ด๋ฏธ ๋‹ค๋ฅธ ๊ณณ์—์„œ ๋‹ต๋ณ€์ด ๋˜์—ˆ๋‹ค๋ฉด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค๋งŒ, ๊ฒ€์ƒ‰ ๋Šฅ๋ ฅ์ด ๋–จ์–ด์กŒ๋‹ค๋ฉด ์ฃ„์†กํ•ฉ๋‹ˆ๋‹ค._

๊ธฐ์กด ํ”„๋กœ๋•์…˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ฝ”๋“œ๋กœ 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; }
        }
    }
}
api-suggestion area-System.Text.Json json-functionality-doc

๊ฐ€์žฅ ์œ ์šฉํ•œ ๋Œ“๊ธ€

  • ์‹œ๊ฐ„ ๋ฒ”์œ„์— ๋Œ€ํ•œ ํ‘œ์ค€ ์ธ์ฝ”๋”ฉ์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ๋ถˆ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์‹œ๊ฐ„ ๋ฒ”์œ„์— ๋Œ€ํ•œ ํ‘œ์ค€ ์ธ์ฝ”๋”ฉ์€ 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 ํ˜•์‹ ๋ฌธ์ž์—ด์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„๊นŒ์š”? ๊ทธ ํ›„์— ๋ณ€ํ™˜๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ์ •๋ง ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“  36 ๋Œ“๊ธ€

์šฐ๋ฆฌ๋Š” ํ˜„์žฌ TimeSpan ๋ฅผ ์ง€์›ํ•  ๊ณ„ํš์ด ์—†์œผ๋ฉฐ ์•ž์œผ๋กœ ์ถ”๊ฐ€๋  ์˜ˆ์ •์ด์ง€๋งŒ ์–ผ๋งˆ๋‚˜ ๋งŽ์€ ์ž‘์—…์ด ๊ด€๋ จ๋˜์–ด ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ธฐ ์œ„ํ•ด ์•ฝ๊ฐ„์˜ ์กฐ์‚ฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜๋Š” ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ TimeSpan ๋ฅผ ์ง€์›ํ•˜๊ธฐ ์œ„ํ•ด ์ž์ฒด JsonConverter ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์ฃผ๋ง๊นŒ์ง€ ์—…๋ฐ์ดํŠธ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ฐ์‚ฌ ํ•ด์š”.

์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด api ๊ฒ€ํ† ๋ฅผ ๊ฑฐ์ณ์•ผ ํ•˜๋Š” reader/writer/JsonElement์— TimeSpan API๋„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ง€์ • ๋ณ€ํ™˜๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด 3.0์šฉ ์•ฑ์—์„œ ์ž‘๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์ด ๋ฏธ๋ฆฌ ๋ณด๊ธฐ 7๊ณผ ํ•จ๊ป˜ ์ œ๊ณต๋  ์˜ˆ์ •์ž…๋‹ˆ๊นŒ?

์˜ˆ, ์‚ฌ์šฉ์ž ์ง€์ • ๋ณ€ํ™˜๊ธฐ ์ง€์›์€ ์ด์ œ ๋งˆ์Šคํ„ฐ์— ์žˆ์œผ๋ฏ€๋กœ ๋ฏธ๋ฆฌ ๋ณด๊ธฐ 7์— ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๊ทธ๋Ÿฌ๋‚˜ TimeSpan์€ ์ผ๋ฐ˜์ ์ธ BCL ์œ ํ˜•์ด๋ฏ€๋กœ ์—ฌ์ „ํžˆ ๊ธฐ๋ณธ ๋ณ€ํ™˜๊ธฐ๋ฅผ ์ œ๊ณตํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์˜ค๋Š˜ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๊ฐ€์น˜ ์œ ํ˜•์ด๊ธฐ ๋•Œ๋ฌธ์— ์ฐฌ์„ฑํ•˜๋Š” ์ฃผ์žฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์›์น™์ ์œผ๋กœ ์šฐ๋ฆฌ๋Š” ํ• ๋‹น์ด ์—†๋„๋ก(์ฆ‰, ๋ฌธ์ž์—ด์„ ๊ฑฐ์น˜์ง€ ์•Š๋„๋ก) ๊ตฌ๋ฌธ ๋ถ„์„์„ ์ตœ์ ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‹œ๊ฐ„ ๋ฒ”์œ„์— ๋Œ€ํ•œ ํ‘œ์ค€ ์ธ์ฝ”๋”ฉ์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ๋ถˆ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค.
  • ์ง€๊ธˆ์€ ๋‹ซ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ํ•„์š”ํ•˜๋‹ค๋Š” ์ถฉ๋ถ„ํ•œ ๊ณ ๊ฐ ์ฆ๊ฑฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ๋‹ค์‹œ ์—ด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‹œ๊ฐ„ ๋ฒ”์œ„์— ๋Œ€ํ•œ ํ‘œ์ค€ ์ธ์ฝ”๋”ฉ์ด ์žˆ๋Š”์ง€ ์—ฌ๋ถ€๋Š” ๋ถˆ๋ถ„๋ช…ํ•ฉ๋‹ˆ๋‹ค.

์‹œ๊ฐ„ ๋ฒ”์œ„์— ๋Œ€ํ•œ ํ‘œ์ค€ ์ธ์ฝ”๋”ฉ์€ 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 ํ˜•์‹ ๋ฌธ์ž์—ด์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์ด ๋” ๋‚˜์„๊นŒ์š”? ๊ทธ ํ›„์— ๋ณ€ํ™˜๊ธฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์€ ์ •๋ง ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

์ด๊ฒƒ์€ ๋˜ํ•œ 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"์ด ์˜ˆ์ƒ๋Œ€๋กœ ๊ตฌ๋ฌธ ๋ถ„์„๋˜์ง€ ์•Š๋Š” ์ด์œ ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ๋Ÿฐํƒ€์ž„ ์ฝ”๋“œ๋ฅผ ์ž ์‹œ ์‚ดํŽด๋ณด๊ณ  ์ด์— ๋Œ€ํ•œ ๋ธ”๋กœ๊ทธ ๊ฒŒ์‹œ๋ฌผ์„ ์ž‘์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค.

์ด ํŽ˜์ด์ง€๊ฐ€ ๋„์›€์ด ๋˜์—ˆ๋‚˜์š”?
0 / 5 - 0 ๋“ฑ๊ธ‰