Runtime: Dukungan JsonSerializer untuk TimeSpan di 3.0?

Dibuat pada 18 Jun 2019  ·  36Komentar  ·  Sumber: dotnet/runtime

_Maaf jika ini sudah dijawab di tempat lain, tetapi jika itu adalah kemampuan pencarian saya, saya gagal._

Saya mencoba pratinjau 6 dari 3.0 dengan beberapa kode aplikasi produksi yang ada, dan salah satu masalah yang muncul dari pengujian adalah bahwa beberapa objek kami yang ada yang kami gunakan dalam kontrak API kami menggunakan properti yaitu TimeSpan nilai, yang kemudian direpresentasikan sebagai string.

Apakah dukungan untuk properti TimeSpan direncanakan untuk 3.0 untuk API System.Text.Json yang baru?

Jika tidak akan memberi kami pemberitahuan untuk melakukan beberapa refactoring sebelum September untuk mengubahnya menjadi string sehingga kami dapat menggunakan serializer baru, di mana seolah-olah direncanakan tetapi belum diterapkan maka kami hanya perlu menunggu pratinjau nanti untuk mendapatkan ini bekerja.

Di bawah ini adalah pengujian unit repro minimal yang menunjukkan kegagalan penanganan TimeSpan dibandingkan dengan kode .NET JSON kami yang ada.

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

Komentar yang paling membantu

  • Tidak jelas apakah ada pengkodean standar untuk rentang waktu.

Pengkodean standar untuk rentang waktu adalah "durasi" ISO8601 . Ini juga yang digunakan XML untuk mewakili TimeSpan dan sudah diimplementasikan di XmlConvert dan 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

Sekarang, bisa dibilang, TimeSpan itu sendiri harus mendukung penguraian dan pemformatan format durasi ISO8601. Mungkin akan lebih baik, sebagai langkah pertama, untuk menambahkan string format TimeSpan standar baru, mirip dengan penentu format pulang-pergi ("O", "o") DateTime[Offset] ? Menambahkan konverter setelah itu akan sangat sederhana.

Semua 36 komentar

Saat ini kami tidak memiliki rencana untuk mendukung TimeSpan dan akan ditambahkan di masa mendatang, tetapi saya dapat melakukan penyelidikan untuk melihat seberapa banyak pekerjaan yang terlibat. Atau, Anda dapat membuat JsonConverter untuk mendukung TimeSpan sebagai solusi. Saya akan memberikan pembaruan pada akhir minggu depan. Terima kasih.

Jika kami ingin menambahkannya, kami juga ingin menambahkan TimeSpan API ke reader/writer/JsonElement, yang harus melalui tinjauan api.

Terima kasih - konverter khusus juga akan menjadi cara yang cukup mudah untuk membuatnya berfungsi untuk aplikasi kami untuk 3.0. Apakah kemampuan tersebut direncanakan untuk dikirimkan dengan pratinjau 7?

Ya, dukungan konverter khusus sekarang ada di master dan dengan demikian akan ada di pratinjau 7.

Namun, karena TimeSpan adalah tipe BCL yang umum, kami masih harus menyediakan konverter default.

Kami meninjau ini hari ini:

  • Ada argumen yang mendukungnya, karena itu adalah tipe nilai. Pada prinsipnya kami dapat mengoptimalkan penguraian agar bebas alokasi (yaitu menghindari melalui string).
  • Tidak jelas apakah ada pengkodean standar untuk rentang waktu.
  • Untuk saat ini, tutup. Ini dapat dibuka kembali jika ada cukup bukti pelanggan bahwa ini diperlukan.
  • Tidak jelas apakah ada pengkodean standar untuk rentang waktu.

Pengkodean standar untuk rentang waktu adalah "durasi" ISO8601 . Ini juga yang digunakan XML untuk mewakili TimeSpan dan sudah diimplementasikan di XmlConvert dan 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

Sekarang, bisa dibilang, TimeSpan itu sendiri harus mendukung penguraian dan pemformatan format durasi ISO8601. Mungkin akan lebih baik, sebagai langkah pertama, untuk menambahkan string format TimeSpan standar baru, mirip dengan penentu format pulang-pergi ("O", "o") DateTime[Offset] ? Menambahkan konverter setelah itu akan sangat sederhana.

Ini juga mengubah perilaku default tentang bagaimana AspNetCore menyerahkan kembali TimeSpan ketik di API, lihat dotnet/AspnetCore#11724. Ini mungkin perubahan yang menghancurkan.

Solusi paling sederhana adalah membuat konverter khusus:

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());
        }
    }

Berbicara tentang bukti pelanggan: untuk dukungan pengembangan perangkat lunak kami untuk TimeSpan sangat diperlukan.

Berlari ke hari ini dengan mem-porting aplikasi ke .NET Core 3.0 juga. Karena ini ditutup, apakah itu berarti Microsoft tidak memiliki rencana untuk menambahkan dukungan asli? Komentar @khellang tampaknya merupakan argumen yang cukup meyakinkan bagi saya bahwa itu harus ada di peta jalan di suatu tempat ...

Pembukaan kembali untuk 5.0 berdasarkan permintaan tambahan. Durasi ISO8601 kemungkinan merupakan representasi terbaik meskipun kompatibilitas dengan Newtonsoft juga harus dipertimbangkan.

Baru saja mengalami masalah ini hari ini. Perilaku default lebih buruk dari perilaku sebelumnya dan sama sekali tidak terduga. Kita harus memperbaikinya, baik dengan menggunakan ISO8601 atau kompatibel dengan Newtonsoft.

Perilaku default lebih buruk dari perilaku sebelumnya dan sama sekali tidak terduga.

@mfeingol Perilaku apa? Bahwa itu hanya gagal?

Kita harus memperbaikinya, baik dengan menggunakan ISO8601 atau kompatibel dengan Newtonsoft.

Sangat mudah untuk menambahkan solusi yang disebutkan @rezabayesteh .

@khellang : apa yang saya amati dengan proyek ASP.NET Core yang relatif vanilla adalah bahwa ia membuat serial Timespan? sebagai bidang HasValue , dan kemudian masing-masing properti dari struct TimeSpan.

Sangat mudah untuk hanya menambahkan solusinya

Saya melakukannya, tetapi itu seharusnya tidak diperlukan untuk tipe yang umum digunakan.

Saya baru saja menemukan masalah ini hari ini (dilaporkan oleh pelanggan saya) dan harus mengalihkan kembali semua webapi dan aplikasi aspnet saya untuk menggunakan serializer Newtonsoft.Json alih-alih menggunakan konfigurasi di Startup.cs:

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

Dalam kasus saya, saya menggunakan beberapa TimeSpan nullable (TimeSpan?) Dan System.Text.Json telah membuat serial sebagai:

{
"hasValue": benar,
"nilai": {
"centang":0,
"hari": 0,
"jam": 0,
"milidetik": 0,
"menit": 0,
"detik": 0,
"totalHari": 0,
"totalJam": 0,
"totalMillidetik": 0,
"totalMenit": 0,
"totalSeconds": 0
}
}

Ini menyebabkan sedikit masalah untuk objek javascript di browser web, serta untuk berbagai deserializer lintas platform (berarti berbagai bahasa pemrograman) yang menggunakan apis saya.

Saya mengharapkan hasil serialisasi yang sama dengan serializer Newtonsoft.Json:

{
"timeSpan": "00:00:00,0000000",
"nullTimeSpan": null
}

Saya baru saja menemukan masalah ini hari ini (dilaporkan oleh pelanggan saya) dan harus mengalihkan kembali semua webapi dan aplikasi aspnet saya untuk menggunakan serializer Newtonsoft.Json sebagai gantinya

@bashocz Mengapa solusi yang disebutkan di https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476 bekerja untuk Anda?

Mengapa solusi yang disebutkan di #38641 (komentar) tidak berhasil untuk Anda?

@khellang Saya hanya ingin menyoroti serialisasi TimeSpan adalah masalah bagi pengembang lain, dan berikan perhatian. Saya sangat ingin beralih ke System.Text.Json, namun ada beberapa kendala.

Saya telah menyelidiki System.Text.Json baru beberapa minggu yang lalu dan ternyata fiturnya tidak lengkap. Saya telah mengangkat masalah dotnet/corefx#41747 dan diarahkan ke dotnet/corefx#39031 lainnya, dotnet/corefx#41325, dotnet/corefx#38650 terkait. Karena itu semua microservice internal kami masih menggunakan Newtonsoft.Json.

Untuk alasan yang tidak diketahui saya lupa mengelola pengembang untuk memperbaikinya di API publik dan aplikasi web juga.

BTW: Saya mencoba untuk menghindari solusi sebanyak mungkin dalam kode produksi .. sulit untuk mempertahankan dan menghapusnya di masa depan.

@khellang , bukan berarti solusi tidak akan berhasil. Ini hanya hal mendasar yang tidak perlu pengembang untuk menambahkan solusi. Sebagai fitur besar yang diperkenalkan untuk .NET core 3, seharusnya tidak kekurangan implementasi dasar seperti itu.

@arisewanggithub Ada pengaturan global yang tersedia untuk pengontrol. Anda dapat mengkonfigurasi melalui AddJsonOptions() , atau Anda dapat lulus JsonSerializerOptions misalnya ke Json() metode controller.

Apakah ini ditutup karena kami memiliki solusi?!

Apakah ini ditutup karena kami memiliki solusi?!

Masalah ini masih terbuka, menunggu proposal, peninjauan, dan implementasi API/perilaku. Sementara itu, cukup mudah untuk mengatasinya dengan menggunakan konverter yang disebutkan di https://github.com/dotnet/corefx/issues/38641#issuecomment -540200476.

Untuk siapa pun yang diblokir oleh ini, inilah paket NuGet dengan konverter (JsonTimeSpanConverter) yang dapat kita gunakan sebelum penurunan 5.0: Macross.Json.Extensions

Mendukung jenis TimeSpan & Nullable<TimeSpan>.

Astaga, ini sakit!

Untuk siapa pun yang diblokir oleh ini, inilah paket NuGet dengan konverter (JsonTimeSpanConverter) yang dapat kita gunakan sebelum penurunan 5.0: Macross.Json.Extensions

Mendukung TimeSpan & Nullablejenis.

Menurut pengujian saya, tipe nilai nullable sudah ditangani oleh framework. Anda tidak perlu lebih dari yang berikut ini:

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));
    }
}

(EDIT: setelah melihat lebih dekat pada sumber kerangka kerja, ternyata pemeriksaan untuk token nol/nilai nol adalah mubazir. Kode di atas diperbarui sesuai dengan itu.)

Kemudian konfigurasikan JsonSerializerOptions seperti ini:

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

Saya tidak berpikir itu ide yang baik untuk menambahkan ketergantungan pihak ke-3 jika Anda dapat mengatasi beberapa baris kode tambahan. Kami tidak di tanah NPM setelah semua. ;)

Tentu saja, akan lebih baik jika kita tidak membutuhkan solusi untuk tipe dasar seperti itu sama sekali.

@adams85 FYI Jenis nilai Nullable disadap < .NET 5 saat digunakan dengan JsonConverterAttribute. Lihat #32006. Saya pribadi lebih suka menggunakan gaya atribut daripada konfigurasi global, oleh karena itu ekstensi & perbaikan bug, tetapi konverter Anda sangat baik. Apakah Anda keberatan jika saya menambahkannya ke Macross.Json.Extensions untuk membantu orang lain yang mungkin juga mendapat manfaat darinya dan tidak keberatan bepergian ke "NPM land" sesekali? :)

@CodeBlanch Terima kasih telah menunjukkan kekhasan ini dengan JsonConverterAttribute keluar. Ini pasti bagian dari cerita lengkap.

Dan, tentu saja, Anda diizinkan untuk menambahkan konverter saya ke lib Anda jika Anda menyukainya. :)

Dimulai dengan .NET 5.0 Pratinjau 5 Saya telah mendapatkan Cannot skip tokens on partial JSON kesalahan round-tripping entitas saya ke JSON dengan System.Text.Json. Baris dan karakter yang menyinggung adalah titik dua di "Ticks":

Bagaimanapun jika saya menggunakan solusi dan membuat serial TimeSpan dengan cara yang masuk akal, itu berfungsi dengan baik.

+1 dari saya karena reaksi awal saya saat membuka file .json untuk memeriksanya hanya untuk menemukan serial TimeSpan dalam semua kemuliaan strukturalnya adalah "pasti tidak..."

Dari @jsedlak di https://github.com/dotnet/runtime/issues/42356 :

Judul Masalah

Kelas System.Text.Json.JsonSerializer tidak dapat melakukan deserialize properti TimeSpan, meskipun dapat membuat serialisasi.

Umum

Contoh proyek yang menunjukkan masalah ini tersedia di sini: https://github.com/jsedlak/TestTimeSpan

Jika Anda mengembalikan properti TimeSpan di WebApi, properti tersebut diserialisasikan dengan benar ke 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 } } ]

Tetapi ekstensi deserializer default ( HttpClient.GetFromJsonAsync ) tidak dapat menangani properti. Ini mengembalikan TimeSpan kosong. Sebuah konverter kustom harus digunakan untuk deserialize objek.

Info DotNet

`.NET Core SDK (mencerminkan semua global.json):
Versi: 3.1.302
Komit: 41faccf259

Lingkungan Waktu Proses:
Nama OS: Windows
Versi OS: 10.0.20201
Platform OS: Windows
RID: win10-x64
Jalur Dasar: C:\Program Files\dotnet\sdk3.1.302\

Tuan rumah (berguna untuk dukungan):
Versi: 3.1.6
Komit: 3acd9b0cd1

.NET Core SDK diinstal:
3.1.302 [C:\Program Files\dotnet\sdk]

.NET Core runtime terinstal:
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]`

Kami ingin membahas ini di .NET 6.0 dan akan menghargai kontribusi komunitas di sini. Solusi untuk sementara adalah dengan menggunakan konverter khusus, misalnya https://github.com/dotnet/runtime/issues/29932#issuecomment -540200476. Implementasinya harus didasarkan pada format ISO8601 agar durasinya konsisten dengan perilaku DateTime dan DateTimeOffset .

Kami ingin membahas ini di .NET 6.0

.NET 5 belum dirilis, saya akan senang jika kita menggigit peluru di v5 (semver mengizinkan perubahan besar yang melanggar v3 => v5) daripada menunggu v6.

Kami tidak dapat memasukkan fitur ini ke dalam versi 5.0. Kami harus memprioritaskan bersama dengan fitur 5.0 lainnya yang berfungsi , dan yang ini tidak cocok. FWIW ini dekat dengan garis potong, sehingga langkahnya terlambat.

Hanya untuk menyoroti solusi sederhana dengan konverter khusus lagi:

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 , saya dapat mengambil ini untuk 6.0.

Saya akan merekomendasikan untuk menggunakan budaya invarian ketika memilih untuk membuat konverter json sendiri:

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));
    }
}

Saya bekerja dengan asp.net core 3.1 dan mengalami masalah ini hari ini. Saya menggunakan teleriks grid dengan signalr dan pengeditan sebaris pada baris yang berisi rentang waktu yang dapat dibatalkan dan dapat melihat bahwa muatan berisi data rentang waktu tetapi deserialisasi gagal. Saya akhirnya menggunakan perpustakaan Macross.Json.Extensions dan mengerjakan ulang beberapa javascript saya untuk membuatnya berfungsi, tidak ideal, saya harap ini mendapatkan perbaikan yang tepat.

Dan jika Anda mencari format ISO8601, Anda dapat menggunakan ini:
```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);
    }
}

```

Harap pertimbangkan TimeSpan setidaknya 24 jam.. misalnya "24:00:00" harus setara dengan new TimeSpan(24, 0, 0)

@gojanpaolo

Harap pertimbangkan TimeSpan minimal 24 jam.. misalnya "24:00:00" harus setara dengan TimeSpan baru (24, 0, 0)

Itu sebenarnya bukan masalah JSON, itu bagian dari logika parse TimeSpan. Jawaban singkat: Gunakan "1.00:00:00" selama 24 jam. Jawaban panjang: Saya menelusuri kode runtime beberapa waktu lalu untuk mencari tahu mengapa "24:00:00" tidak mengurai seperti yang diharapkan dan menulis posting blog tentangnya.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat