Runtime: 3.0 中对 TimeSpan 的 JsonSerializer 支持?

创建于 2019-06-18  ·  36评论  ·  资料来源: dotnet/runtime

_抱歉,如果这已经在其他地方得到了回答,但如果是我的搜索能力让我失败了。_

我正在使用一些现有的生产应用程序代码尝试 3.0 的预览版 6,测试中出现的问题之一是我们在 API 合同中使用的一些现有对象使用了TimeSpan属性值,然后将其表示为字符串。

是否计划为 3.0 的新 System.Text.Json API 支持TimeSpan属性?

如果它不会通知我们在 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 用来表示TimeSpan并且已经在XmlConvertXsdDuration

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 持续时间格式。 也许作为第一步,添加一个新的标准TimeSpan格式字符串会更好,类似于DateTime[Offset]的往返(“O”,“o”)格式说明符? 在此之后添加转换器将非常简单。

所有36条评论

我们目前没有支持TimeSpan ,将来会添加,但我可以做一些调查,看看涉及多少工作。 或者,您可以创建自己的JsonConverter以支持TimeSpan作为解决方法。 我会在下周末之前提供更新。 谢谢。

如果我们要添加它,我们还想将TimeSpan API 添加到 reader/writer/JsonElement,这将需要经过 api 审查。

谢谢 - 自定义转换器也是一种足够简单的方法,可以使其适用于我们的 3.0 应用程序。 这些功能是否计划随预览版 7 一起提供?

是的,自定义转换器支持现在在 master 中,因此将在预览 7 中。

但是,由于 TimeSpan 是常见的 BCL 类型,我们仍应提供默认转换器。

我们今天回顾了这一点:

  • 有一个支持它的论据,因为它是一种值类型。 原则上我们可以将解析优化为无需分配(即避免通过字符串)。
  • 目前尚不清楚是否有时间跨度的标准编码。
  • 现在,关闭。 如果有足够的客户证据表明需要重新打开,则可以重新打开。
  • 目前尚不清楚是否有时间跨度的标准编码。

时间跨度的标准编码是ISO8601 的 "duration" 。 这也是XML 用来表示TimeSpan并且已经在XmlConvertXsdDuration

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 持续时间格式。 也许作为第一步,添加一个新的标准TimeSpan格式字符串会更好,类似于DateTime[Offset]的往返(“O”,“o”)格式说明符? 在此之后添加转换器将非常简单。

这也改变了 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 :我在一个相对普通的 ASP.NET Core 项目中观察到的是,它将Timespan?序列化为HasValue字段,然后是 TimeSpan 结构的每个属性。

添加解决方法真的很容易

我做到了,但对于这种常用类型,这不是必需的。

我今天刚刚发现这个问题(由我的客户报告)并且必须切换回我所有的 aspnet webapi 和应用程序以使用 Newtonsoft.Json 序列化程序而不是使用 Startup.cs 中的配置:

services.AddControllers()
.AddNewtonsoftJson(选项=>
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
});

在我的情况下,我使用了一些可为空的 TimeSpan (TimeSpan?) 并且 System.Text.Json 已将其序列化为:

{
“hasValue”:真,
“价值”: {
“滴答”:0,
“天”:0,
“小时”:0,
“毫秒”:0,
“分钟”:0,
“秒”:0,
“总天数”:0,
“总小时数”:0,
"totalMilliseconds": 0,
“总分钟数”:0,
“总秒数”:0
}
}

这会导致 Web 浏览器中的 javascript 对象以及使用我的 api 的各种跨平台(意味着各种编程语言)反序列化器出现一些问题。

我希望与 Newtonsoft.Json 序列化器相同的序列化结果:

{
"timeSpan": "00:00:00.0000000",
“nullTimeSpan”:空
}

我今天刚刚发现这个问题(由我的客户报告)并且必须切换回我所有的 aspnet webapi 和应用程序以使用 Newtonsoft.Json 序列化程序

@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 和 web 应用程序上修复它。

顺便说一句:我尽量避免在生产代码中使用变通方法..将来很难维护和删除它。

@khellang ,并不是说解决方法不起作用。 这只是一个基本的事情,不需要开发人员添加解决方法。 作为 .NET Core 3 引入的一大特性,它不应该缺少这样的基本实现。

@arisewanggithub有可用于控制器的全局设置。 您可以通过AddJsonOptions()对其进行配置,也可以将JsonSerializerOptions实例传递给Json()控制器方法。

这是否已关闭,因为我们有解决方法?!

这是否已关闭,因为我们有解决方法?!

该问题仍然悬而未决,等待 API/行为提案、审查和实施。 同时,使用https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476 中提到的转换器很容易解决。

对于被此阻止的任何人,这里有一个带有转换器 (JsonTimeSpanConverter) 的 NuGet 包,我们可以在 5.0 发布之前使用: Macross.Json.Extensions

支持 TimeSpan 和 Nullable<TimeSpan> 类型。

哦,该死,这很痛!

对于被此阻止的任何人,这里有一个带有转换器 (JsonTimeSpanConverter) 的 NuGet 包,我们可以在 5.0 发布之前使用: Macross.Json.Extensions

支持 TimeSpan 和 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));
    }
}

(编辑:在仔细查看框架源之后,结果发现对空标记/空值的检查是多余的。上面的代码已相应更新。)

然后像这样配置JsonSerializerOptions

options.Converters.Add(new DelegatedStringJsonConverter<TimeSpan>(
    value => TimeSpan.Parse(value, CultureInfo.InvariantCulture),
    value => value.ToString(null, CultureInfo.InvariantCulture)));

如果您可以处理这么几行额外的代码,我认为添加 3rd 方依赖项不是一个好主意。 毕竟我们不在 NPM 领域。 ;)

当然,如果我们根本不需要这种基本类型的解决方法,那就最好了。

@adams85 FYI Nullable 值类型在与 JsonConverterAttribute 一起使用时被窃听 <.NET 5。 见#32006。 我个人更喜欢使用属性样式而不是全局配置,因此扩展和错误修复,但你的转换器非常好。 你介意我将它添加到 Macross.Json.Extensions 以帮助其他人也可以从中受益并且不介意偶尔前往“NPM 土地”吗? :)

@CodeBlanch感谢您指出JsonConverterAttribute这个怪癖。 这绝对是整个故事的一部分。

而且,当然,如果您喜欢,您可以将我的转换器添加到您的库中。 :)

从 .NET 5.0 Preview 5 开始,我一直收到Cannot skip tokens on partial JSON错误,使用 System.Text.Json 将我的实体往返转换为 JSON。 有问题的行和字符是"Ticks":的冒号

无论如何,如果我使用变通方法并以合理的方式序列化TimeSpan它工作正常。

来自我的 +1,因为我打开.json文件检查它时的最初反应只是发现TimeSpan在其所有结构荣耀中序列化是“肯定不是......”

来自https://github.com/dotnet/runtime/issues/42356 中的@jsedlak

问题标题

System.Text.Json.JsonSerializer 类无法反序列化 TimeSpan 属性,即使它可以序列化它。

一般的

演示此问题的示例项目可在此处获得: https :

如果您在 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 } } ]

但是默认的反序列化器扩展 ( HttpClient.GetFromJsonAsync ) 无法处理该属性。 它返回一个空的 TimeSpan。 必须使用自定义转换器来反序列化对象。

点网信息

`.NET Core SDK(反映任何 global.json):
版本:3.1.302
提交:41faccf259

运行环境:
操作系统名称:Windows
操作系统版本:10.0.20201
操作系统平台:Windows
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。 为了与DateTimeDateTimeOffset

我们想在 .NET 6.0 中解决这个问题

.NET 5 尚未发布,如果我们在 v5 中咬紧牙关(semver 允许重大突破性更改 v3 => v5)而不是等待 v6,我会很高兴。

我们无法将此功能放入 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 ,我可以把它

我建议在选择自己构建 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,今天遇到了这个问题。 我在包含可为空时间跨度的行上使用带有信号器和内联编辑的 Teleriks 网格,并且可以看到有效负载包含时间跨度数据但反序列化失败。 我最终使用了 Macross.Json.Extensions 库并重新编写了我的一些 javascript 以使其工作,虽然并不理想,但我希望这能得到适当的修复。

如果你正在寻找 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 等级

相关问题

bencz picture bencz  ·  3评论

nalywa picture nalywa  ·  3评论

yahorsi picture yahorsi  ·  3评论

chunseoklee picture chunseoklee  ·  3评论

omariom picture omariom  ·  3评论