Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions src/serialization/json/DateJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Kiota.Abstractions;

namespace Microsoft.Kiota.Serialization.Json;

/// <summary>
/// Converts a Date object or value to/from JSON.
/// </summary>
public class DateJsonConverter : JsonConverter<Date>
{
/// <inheritdoc />
public override Date Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType == JsonTokenType.Null)
return default;

if(reader.TokenType == JsonTokenType.String)
{
var stringValue = reader.GetString();
if(string.IsNullOrEmpty(stringValue))
return default;

if(DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
return new Date(dateTime);

throw new JsonException($"Unable to parse '{stringValue}' as a Date.");
}

throw new JsonException($"Unexpected token type '{reader.TokenType}' when reading Date.");
}

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Date value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
23 changes: 22 additions & 1 deletion src/serialization/json/KiotaJsonSerializationContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Kiota.Abstractions;

Expand Down Expand Up @@ -34,4 +35,24 @@ namespace Microsoft.Kiota.Serialization.Json;
[JsonSerializable(typeof(Date?))]
[JsonSerializable(typeof(Time))]
[JsonSerializable(typeof(Time?))]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious: should these attributes be updated to point to the converters as well?

public partial class KiotaJsonSerializationContext : JsonSerializerContext;
public partial class KiotaJsonSerializationContext : JsonSerializerContext
{
private static JsonSerializerOptions? _defaultOptionsWithConverters;

/// <summary>
/// Gets the default <see cref="JsonSerializerOptions"/> with Date and Time converters registered.
/// </summary>
public static JsonSerializerOptions DefaultOptionsWithConverters
{
get
{
if(_defaultOptionsWithConverters == null)
{
_defaultOptionsWithConverters = new JsonSerializerOptions(Default.Options);
_defaultOptionsWithConverters.Converters.Add(new DateJsonConverter());
_defaultOptionsWithConverters.Converters.Add(new TimeJsonConverter());
}
return _defaultOptionsWithConverters;
}
}
}
44 changes: 44 additions & 0 deletions src/serialization/json/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,50 @@ Read more about Kiota [here](https://github.com/microsoft/kiota/blob/main/README
dotnet add package Microsoft.Kiota.Serialization.Json
```

## Using Date and Time Converters with System.Text.Json

The library provides `DateJsonConverter` and `TimeJsonConverter` to enable serialization and deserialization of Kiota's `Date` and `Time` types when using `System.Text.Json.JsonSerializer` directly (outside of Kiota's serialization infrastructure).

### Option 1: Using DefaultOptionsWithConverters

The simplest approach is to use the pre-configured options with converters:

```csharp
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Serialization.Json;
using System.Text.Json;

var date = new Date(2025, 10, 24);
var json = JsonSerializer.Serialize(date, KiotaJsonSerializationContext.DefaultOptionsWithConverters);
// Output: "2025-10-24"

var deserialized = JsonSerializer.Deserialize<Date>(json, KiotaJsonSerializationContext.DefaultOptionsWithConverters);
// deserialized.Year == 2025, deserialized.Month == 10, deserialized.Day == 24
```

### Option 2: Manually Registering Converters

You can also manually register the converters with your own `JsonSerializerOptions`:

```csharp
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Serialization.Json;
using System.Text.Json;

var options = new JsonSerializerOptions();
options.Converters.Add(new DateJsonConverter());
options.Converters.Add(new TimeJsonConverter());

var time = new Time(10, 18, 54);
var json = JsonSerializer.Serialize(time, options);
// Output: "10:18:54"

var deserialized = JsonSerializer.Deserialize<Time>(json, options);
// deserialized.Hour == 10, deserialized.Minute == 18, deserialized.Second == 54
```

This is particularly useful when integrating with third-party libraries that serialize/deserialize Kiota-generated models but cannot use Kiota's serialization infrastructure.

## Debugging

If you are using Visual Studio Code as your IDE, the **launch.json** file already contains the configuration to build and test the library. Otherwise, you can open the **Microsoft.Kiota.Serialization.Json.sln** with Visual Studio.
Expand Down
44 changes: 44 additions & 0 deletions src/serialization/json/TimeJsonConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Kiota.Abstractions;

namespace Microsoft.Kiota.Serialization.Json;

/// <summary>
/// Converts a Time object or value to/from JSON.
/// </summary>
public class TimeJsonConverter : JsonConverter<Time>
{
/// <inheritdoc />
public override Time Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType == JsonTokenType.Null)
return default;

if(reader.TokenType == JsonTokenType.String)
{
var stringValue = reader.GetString();
if(string.IsNullOrEmpty(stringValue))
return default;

if(DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out var dateTime))
return new Time(dateTime);

throw new JsonException($"Unable to parse '{stringValue}' as a Time.");
}

throw new JsonException($"Unexpected token type '{reader.TokenType}' when reading Time.");
}

/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, Time value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
99 changes: 99 additions & 0 deletions tests/serialization/json/DateJsonConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Text.Json;
using Microsoft.Kiota.Abstractions;
using Xunit;

namespace Microsoft.Kiota.Serialization.Json.Tests;

public class DateJsonConverterTests
{
private readonly JsonSerializerOptions _options;

public DateJsonConverterTests()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new DateJsonConverter());
}

[Fact]
public void SerializesDateAsString()
{
var date = new Date(2025, 10, 24);
var json = JsonSerializer.Serialize(date, _options);

Assert.Equal("\"2025-10-24\"", json);
}

[Fact]
public void DeserializesDateFromString()
{
var json = "\"2025-10-24\"";
var date = JsonSerializer.Deserialize<Date>(json, _options);

Assert.Equal(2025, date.Year);
Assert.Equal(10, date.Month);
Assert.Equal(24, date.Day);
}

[Fact]
public void RoundTripSerialization()
{
var original = new Date(2025, 10, 24);
var json = JsonSerializer.Serialize(original, _options);
var deserialized = JsonSerializer.Deserialize<Date>(json, _options);

Assert.Equal(original.Year, deserialized.Year);
Assert.Equal(original.Month, deserialized.Month);
Assert.Equal(original.Day, deserialized.Day);
}

[Fact]
public void DeserializesNullToDefault()
{
var json = "null";
var date = JsonSerializer.Deserialize<Date>(json, _options);

Assert.Equal(default(Date), date);
}

[Fact]
public void DeserializesEmptyStringToDefault()
{
var json = "\"\"";
var date = JsonSerializer.Deserialize<Date>(json, _options);

Assert.Equal(default(Date), date);
}

[Fact]
public void ThrowsOnInvalidFormat()
{
var json = "\"not-a-date\"";

Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Date>(json, _options));
}

[Fact]
public void ThrowsOnUnexpectedTokenType()
{
var json = "123";

Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Date>(json, _options));
}

[Fact]
public void WorksWithDefaultOptionsWithConverters()
{
var date = new Date(2025, 10, 24);
var json = JsonSerializer.Serialize(date, KiotaJsonSerializationContext.DefaultOptionsWithConverters);
var deserialized = JsonSerializer.Deserialize<Date>(json, KiotaJsonSerializationContext.DefaultOptionsWithConverters);

Assert.Equal(date.Year, deserialized.Year);
Assert.Equal(date.Month, deserialized.Month);
Assert.Equal(date.Day, deserialized.Day);
}
}
99 changes: 99 additions & 0 deletions tests/serialization/json/TimeJsonConverterTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// ------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information.
// ------------------------------------------------------------------------------

using System;
using System.Text.Json;
using Microsoft.Kiota.Abstractions;
using Xunit;

namespace Microsoft.Kiota.Serialization.Json.Tests;

public class TimeJsonConverterTests
{
private readonly JsonSerializerOptions _options;

public TimeJsonConverterTests()
{
_options = new JsonSerializerOptions();
_options.Converters.Add(new TimeJsonConverter());
}

[Fact]
public void SerializesTimeAsString()
{
var time = new Time(10, 18, 54);
var json = JsonSerializer.Serialize(time, _options);

Assert.Equal("\"10:18:54\"", json);
}

[Fact]
public void DeserializesTimeFromString()
{
var json = "\"10:18:54\"";
var time = JsonSerializer.Deserialize<Time>(json, _options);

Assert.Equal(10, time.Hour);
Assert.Equal(18, time.Minute);
Assert.Equal(54, time.Second);
}

[Fact]
public void RoundTripSerialization()
{
var original = new Time(10, 18, 54);
var json = JsonSerializer.Serialize(original, _options);
var deserialized = JsonSerializer.Deserialize<Time>(json, _options);

Assert.Equal(original.Hour, deserialized.Hour);
Assert.Equal(original.Minute, deserialized.Minute);
Assert.Equal(original.Second, deserialized.Second);
}

[Fact]
public void DeserializesNullToDefault()
{
var json = "null";
var time = JsonSerializer.Deserialize<Time>(json, _options);

Assert.Equal(default(Time), time);
}

[Fact]
public void DeserializesEmptyStringToDefault()
{
var json = "\"\"";
var time = JsonSerializer.Deserialize<Time>(json, _options);

Assert.Equal(default(Time), time);
}

[Fact]
public void ThrowsOnInvalidFormat()
{
var json = "\"not-a-time\"";

Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Time>(json, _options));
}

[Fact]
public void ThrowsOnUnexpectedTokenType()
{
var json = "123";

Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<Time>(json, _options));
}

[Fact]
public void WorksWithDefaultOptionsWithConverters()
{
var time = new Time(10, 18, 54);
var json = JsonSerializer.Serialize(time, KiotaJsonSerializationContext.DefaultOptionsWithConverters);
var deserialized = JsonSerializer.Deserialize<Time>(json, KiotaJsonSerializationContext.DefaultOptionsWithConverters);

Assert.Equal(time.Hour, deserialized.Hour);
Assert.Equal(time.Minute, deserialized.Minute);
Assert.Equal(time.Second, deserialized.Second);
}
}