I have an object as a body on POST request in my ASP.NET Core 8 application. The object is not serialized, and we found out that the property that is causing all is one with type ZonedDateTime and it has a value:
"completedTimestamp": "2023-05-18T14:39:55.303+02:00[Europe/Budapest]",
We are using Newtonsodt.Json library and configured the serializer settings as:
public static IMvcBuilder ConfigureApiBehaviorOptions(this IMvcBuilder builder)
{
builder.AddNewtonsoftJson(options =>
{
var pattern = ZonedDateTimePattern.CreateWithInvariantCulture("yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFo<Z+HH:mm>'['z']'", DateTimeZoneProviders.Serialization);
options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
options.SerializerSettings.Converters.Add(new NodaPatternConverter<ZonedDateTime>(pattern));
});
return builder;
}
When the request hits the controller my object is null, if I remove all properties of ZonedDateTime it got mapped.
If I try this with a a console app it works. My console app:
var pattern = ZonedDateTimePattern.CreateWithInvariantCulture(
"yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFo<Z+HH:mm>'['z']'",
DateTimeZoneProviders.Serialization
);
var settings = new JsonSerializerSettings();
settings.DateParseHandling = DateParseHandling.None;
settings.Converters.Add(new NodaPatternConverter<ZonedDateTime>(pattern));
var json = "{\r\n\t\"completedTimestamp\": \"2023-05-18T14:39:55.303+02:00[Europe/Budapest]\"\r\n}";
var data = JsonConvert.DeserializeObject<Data>(json, settings);
Below is sample code you could try:
Converters/ZonedDateTimeConverter.cs:
using Newtonsoft.Json;
using NodaTime;
using NodaTime.Text;
using System;
namespace NodaTimeSample.Converters
{
public class ZonedDateTimeConverter : JsonConverter<ZonedDateTime>
{
private readonly ZonedDateTimePattern _pattern;
public ZonedDateTimeConverter()
{
_pattern = ZonedDateTimePattern.CreateWithInvariantCulture(
"yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFo<Z+HH:mm>'['z']'",
DateTimeZoneProviders.Tzdb);
}
public override void WriteJson(JsonWriter writer, ZonedDateTime value, JsonSerializer serializer)
{
writer.WriteValue(_pattern.Format(value));
}
public override ZonedDateTime ReadJson(JsonReader reader, Type objectType, ZonedDateTime existingValue, bool hasExistingValue, JsonSerializer serializer)
{
var text = reader.Value?.ToString();
Console.WriteLine($"Attempting to parse: {text}");
var result = _pattern.Parse(text);
if (result.Success)
{
return result.Value;
}
Console.WriteLine($"Failed to parse ZonedDateTime: {text}");
throw new JsonSerializationException($"Invalid ZonedDateTime format: {text}");
}
}
}
Data.cs:
using NodaTime;
namespace NodaTimeSample.Models
{
public class Data
{
public ZonedDateTime CompletedTimestamp { get; set; }
}
}
MyController:
using Microsoft.AspNetCore.Mvc;
using NodaTimeSample.Models;
namespace NodaTimeSample.Controllers
{
[ApiController]
[Route("[controller]")]
public class MyController : ControllerBase
{
[HttpPost]
public IActionResult Post([FromBody] Data data)
{
if (data == null)
{
Console.WriteLine("Data is null");
return BadRequest("Data is null");
}
Console.WriteLine($"Received data: {data.CompletedTimestamp}");
return Ok(data);
}
}
}
Program:
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using NodaTime;
using NodaTime.Serialization.JsonNet;
using NodaTime.Text;
using NodaTimeSample.Converters;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers()
.AddNewtonsoftJson(options =>
{
var pattern = ZonedDateTimePattern.CreateWithInvariantCulture(
"yyyy'-'MM'-'dd'T'HH':'mm':'ss;FFFo<Z+HH:mm>'['z']'",
DateTimeZoneProviders.Serialization
);
options.SerializerSettings.MissingMemberHandling = MissingMemberHandling.Ignore;
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
options.SerializerSettings.Converters.Add(new NodaPatternConverter<ZonedDateTime>(pattern));
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Result: