Приношу свои извинения, если на этот вопрос уже ответили где-то еще, но если это были мои поисковые способности, я потерпел неудачу.
Я пробую превью 6 версии 3.0 с некоторым существующим кодом производственного приложения, и одна из проблем, возникающих в результате тестирования, заключается в том, что некоторые из наших существующих объектов, которые мы используем в наших контрактах API, используют свойства, которые являются TimeSpan
значения, которые затем представлены в виде строк.
Планируется ли поддержка свойств TimeSpan
в версии 3.0 для новых API-интерфейсов System.Text.Json?
Если этого не произойдет, это дает нам уведомление о необходимости провести некоторый рефакторинг до сентября, чтобы преобразовать их в строки, чтобы мы могли использовать новый сериализатор, где, как если бы он планировался, но просто еще не реализован, нам просто нужно дождаться более позднего предварительного просмотра чтобы это работало.
Ниже приведен минимальный модульный тест воспроизведения, демонстрирующий сбой обработки TimeSpan
по сравнению с нашим существующим кодом JSON .NET.
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
и мы будем добавлены в будущем, но я могу провести небольшое расследование, чтобы увидеть, сколько работы потребуется. В качестве альтернативы вы можете создать свой собственный JsonConverter
для поддержки TimeSpan
в качестве обходного пути. Я дам обновленную информацию к концу следующей недели. Спасибо.
Если бы мы добавили его, мы бы также захотели добавить TimeSpan
API в reader / writer / JsonElement, который должен был бы пройти проверку api.
Спасибо - настраиваемый конвертер также был бы достаточно простым способом заставить его работать в нашем приложении для версии 3.0. Планируется ли появление этих возможностей в предварительной версии 7?
Да, поддержка настраиваемого конвертера теперь находится в главном и, следовательно, будет в предварительной версии 7.
Однако, поскольку TimeSpan является распространенным типом BCL, мы все же должны предоставить преобразователь по умолчанию.
Мы рассмотрели это сегодня:
- Неясно, существует ли стандартная кодировка для временных интервалов.
Стандартной кодировкой временных интервалов будет «продолжительность» ISO8601 . Это также то, что XML использует для представления TimeSpan
и уже реализовано в XmlConvert
и XsdDuration
:
Теперь, возможно, сам TimeSpan
должен поддерживать синтаксический анализ и форматирование форматов продолжительности ISO8601. Может быть, было бы лучше в качестве первого шага добавить новую стандартную строку формата TimeSpan
, аналогичную DateTime[Offset]
("O", "o")? Добавить конвертер после этого было бы очень просто.
Это также изменило поведение по умолчанию того, как AspNetCore возвращает тип TimeSpan
в API, см. 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 на основании дополнительных запросов. Продолжительность ISO8601, вероятно, является лучшим представлением, хотя также следует учитывать совместимость с Newtonsoft.
Просто столкнулся с этой проблемой сегодня. Поведение по умолчанию хуже, чем предыдущее, и было совершенно неожиданным. Мы должны исправить это, используя ISO8601 или совместимость с Newtonsoft.
Поведение по умолчанию хуже, чем предыдущее, и было совершенно неожиданным.
@mfeingol Какое поведение? Что это просто не удается?
Мы должны исправить это, используя ISO8601 или совместимость с Newtonsoft.
Очень легко просто добавить обходной путь, упомянутый
@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,
"totalDays": 0,
«totalHours»: 0,
"totalMilliseconds": 0,
"totalMinutes": 0,
«totalSeconds»: 0
}
}
Это вызывает небольшую проблему для объектов 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, и в веб-приложениях.
Кстати: я стараюсь по возможности избегать обходных путей в производственном коде ... его трудно поддерживать и удалять в будущем.
@khellang , дело не в том, что обходной путь не сработает. Это такая простая вещь, которая не требует от разработчиков добавления обходного пути. Поскольку .NET Core 3 представляет собой большую функцию, в ней не должно быть недостатка в таких базовых реализациях.
@arisewanggithub Для контроллеров доступны глобальные настройки. Вы можете настроить его с помощью AddJsonOptions()
, или вы можете передать JsonSerializerOptions
экземпляра в Json()
контроллера метода.
Это закрытая причина, по которой у нас есть обходной путь ?!
Это закрытая причина, по которой у нас есть обходной путь ?!
Проблема все еще открыта, ожидается предложение, рассмотрение и реализация API / поведения. А пока довольно легко обойтись, используя конвертер, упомянутый в https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476.
Для всех, кто заблокирован этим, вот пакет NuGet с конвертером (JsonTimeSpanConverter), который мы можем использовать перед выпуском 5.0:
Поддерживает типы TimeSpan и Nullable <TimeSpan>.
Вот дерьмо, больно!
Для всех, кто заблокирован этим, вот пакет NuGet с конвертером (JsonTimeSpanConverter), который мы можем использовать перед выпуском 5.0:
Поддерживает TimeSpan и Nullable
типы.
Согласно моим тестам, фреймворк уже обрабатывает типы значений, допускающие значение NULL. Вам не потребуется ничего, кроме следующего:
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)));
Я не думаю, что добавление сторонней зависимости - хорошая идея, если вы можете справиться с таким несколькими строками дополнительного кода. В конце концов, мы не на земле НПМ. ;)
Конечно, было бы лучше, если бы нам вообще не требовалось обходное решение для такого базового типа.
@ adams85 FYI Типы значений, NULL, ошибаются <.NET 5 при использовании с JsonConverterAttribute. См. № 32006. Я лично предпочитаю использовать стиль атрибута, а не глобальную конфигурацию, поэтому исправлены расширения и ошибки, но ваш конвертер отличный. Вы не возражаете, если я добавлю его в Macross.Json.Extensions, чтобы помочь кому-либо еще, кто также может извлечь из этого выгоду, и не возражает против того, чтобы при случае поехать в «страну НПМ»? :)
@CodeBlanch Спасибо, что указали на эту причуду с помощью JsonConverterAttribute
out. Это определенно часть всей истории.
И, конечно же, вы можете добавить мой конвертер в вашу библиотеку, если вам это нравится. :)
Начиная с .NET 5.0 Preview 5, я получал ошибки Cannot skip tokens on partial JSON
переводящие мои объекты в JSON с помощью System.Text.Json. Оскорбительная строка и символ - это двоеточие в "Ticks":
В любом случае, если я использую обходной путь и разумно сериализую TimeSpan
он работает нормально.
+1 от меня, потому что моя первоначальная реакция при открытии файла .json
для его проверки только для того, чтобы найти сериализованный TimeSpan
во всей его структурной красоте, была "конечно же не ..."
Из @jsedlak в https://github.com/dotnet/runtime/issues/42356 :
Заголовок выпуска
Класс System.Text.Json.JsonSerializer не может десериализовать свойство TimeSpan, даже если он может сериализовать его.
Общий
Пример проекта, демонстрирующего эту проблему, доступен здесь: https://github.com/jsedlak/TestTimeSpan
Если вы возвращаете свойство TimeSpan в WebApi, оно правильно сериализуется в 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. Для десериализации объекта необходимо использовать специальный преобразователь.Информация о DotNet
`.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Установленные SDK .NET Core:
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. Реализация должна быть основана на формате ISO8601, чтобы продолжительность согласовывалась с поведением для DateTime
и DateTimeOffset
.
Мы хотели бы решить эту проблему в .NET 6.0.
.NET 5 еще не выпущен, мне бы хотелось, чтобы мы укусили пулю в v5 (semver допускает серьезные критические изменения v3 => v5) вместо того, чтобы ждать v6.
Мы не смогли уместить эту функцию в 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 и сегодня столкнулся с этой проблемой. Я использую сетку teleriks с signalr и встроенным редактированием строк, которые содержат временные интервалы, допускающие значение NULL, и вижу, что полезная нагрузка содержит данные временных интервалов, но десериализация не выполняется. В итоге я использовал библиотеку Macross.Json.Extensions и переделал некоторые из моих javascript, чтобы заставить его работать, хотя и не идеально, надеюсь, это будет исправлено.
И если вы ищете формат ISO8601, вы можете использовать это:
`` С #
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);
}
}
`` ''
Пожалуйста, примите во внимание TimeSpan
не менее 24 часов .. например, "24:00:00"
должно быть эквивалентно new TimeSpan(24, 0, 0)
@gojanpaolo
Пожалуйста, примите во внимание, что TimeSpan составляет не менее 24 часов .. например, "24:00:00" должно быть эквивалентно новому TimeSpan (24, 0, 0)
На самом деле это не JSON, это часть логики синтаксического анализа TimeSpan. Краткий ответ: используйте «1,00: 00: 00» в течение 24 часов. Длинный ответ: я некоторое время назад изучал код времени выполнения, чтобы выяснить, почему «24:00:00» не разбирается так, как ожидалось, и написал об этом сообщение в блоге.
Самый полезный комментарий
Стандартной кодировкой временных интервалов будет «продолжительность» ISO8601 . Это также то, что XML использует для представления
TimeSpan
и уже реализовано в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. Может быть, было бы лучше в качестве первого шага добавить новую стандартную строку форматаTimeSpan
, аналогичнуюDateTime[Offset]
("O", "o")? Добавить конвертер после этого было бы очень просто.