Custom System.Text JsonConverter<DateTimeOffset> not getting invoked - c#

I am creating a custom JsonConverterto parse datetimeoffset, to fix utc issue with offset. I am following MS doc
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace SystemTextJsonSamples
{
public class DateTimeOffsetJsonConverter : JsonConverter<DateTimeOffset>
{
public override DateTimeOffset Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options) =>
DateTimeOffset.ParseExact(reader.GetString()!,
"MM/dd/yyyy", CultureInfo.InvariantCulture);
public override void Write(
Utf8JsonWriter writer,
DateTimeOffset dateTimeValue,
JsonSerializerOptions options) =>
writer.WriteStringValue(dateTimeValue.ToString(
"MM/dd/yyyy", CultureInfo.InvariantCulture));
}
}
I have registered the converter in the startup like so
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
options.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter());
})
and here is my model
[Serializable()]
public class Travel
{
public DateTimeOffset TravelTime { get; set; }
}
When i make call to my api, my custom converter for datetimeoffset is not getting called. Please note that i also have a customdate converter which is working as expected.
Why is my offsetdatetime converter not getting invoked when i serialize/deserialize.
I am using .Net core 6

It's not enough to define a JsonConverter, you also have to apply it to the property, like this:
[JsonConverter(typeof(DateTimeOffsetJsonConverter))]
public DateTimeOffset TravelTime { get; set; }
Then it will be used for serialization and deserialization.
The reason for this (like why can't it pick up the type?) is that you can have several converters defined for the same type and apply them to the properties that need them. Now you can actually make different 'string to string' converters (for example).

Firstly, it should be DateTimeOffsetJsonConverter instead of DateTimeOffsetConverter, change your code to:
builder.Services.AddControllersWithViews().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
options.JsonSerializerOptions.Converters.Add(new DateTimeConverter());
//options.JsonSerializerOptions.Converters.Add(new DateTimeOffsetConverter());
options.JsonSerializerOptions.Converters.Add(new DateTimeOffsetJsonConverter());
});
Then, be sure you post the data with content type application/json. For example:
Besides, you can also add [FromBody] to specify the source because it binds the form data by default if you use asp.net core MVC project:
[HttpPost]
public IActionResult Index([FromBody]Travel model)
{
//do your stuff...
}

Related

Trimming all posted strings to an API ASP.NET CORE 3.x

Information for this as to the many .Net versions is all over the place and I cannot find a concrete up to date example.
I am attempting to automatically trim all the “string” values I post to my API’s automatically.
Note this is ASP.NET CORE 3.x which has introduced the new namespaces “System.Text.Json” etc. Rather than the Newtonsoft ones most old examples are using.
The Core 3.x API’s do not use Model Binding but rather JsonConverter(s) that I am attempting to override, therefore model binding examples are not relevant here.
The following code does work but it means that I must put the annotation:
[JsonConverter(typeof(TrimStringConverter))]
Above each string in the API models I am posting too.
How can I do this so that it just does it to anything defined as a string in all the API models globally?
// TrimStringConverter.cs
// Used https://github.com/dotnet/runtime/blob/81bf79fd9aa75305e55abe2f7e9ef3f60624a3a1/src/libraries/System.Text.Json/src/System/Text/Json/Serialization/Converters/JsonValueConverterString.cs
// as a template From the DotNet source.
using System;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace User
{
public class TrimStringConverter : JsonConverter<string?>
{
public override string? Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
return reader.GetString().Trim();
}
public override void Write(
Utf8JsonWriter writer,
string? value,
JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
}
// CreateUserApiModel.cs
using System.Text.Json.Serialization;
namespace User
{
public class CreateUserApiModel
{
// This one will get trimmed with annotation.
[JsonConverter(typeof(TrimStringConverter))]
public string FirstName { get; set; }
// This one will not.
public string LastName { get; set; }
}
}
// ApiController
[HttpPost]
[Route("api/v1/user/create")]
public async Task<IActionResult> CreateUserAsync(CreateUserApiModel createUserApiModel)
{
// createUserApiModel.FirstName << Will be trimmed.
// createUserApiModel.LastName, << Wont be trimmed.
return Ok("{}");
}
As to the comment by #pinkfloydx33 above the following seems to work correctly.
// Startup.cs
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new TrimStringConverter());
});
Also for note, anybody using the code in the original question. The Converter only trims on Read and not on write unless you add another trim(). I only required it one way.

Cannot convert string to TimeSpan

So, i am creating a service (using ASP.NETCore 3.1) that receives information about the operation schedule of a shop that can be registered in the system. For that i have an entity which communicates to the database and a model which communicates with this entity, as follows:
Entity:
public class OperationSchedule
{
public int Id { get; set; }
public TimeSpan Open { get; set; }
public TimeSpan Close { get; set; }
public DayOfWeek DatofWeek { get; set; }
}
Model:
public class OperationScheduleModel
{
public TimeSpan Open { get; set; }
public TimeSpan Close { get; set; }
public DayOfWeek DatofWeek { get; set; }
}
Of course they were already mapped using automapper. I'm using swagger to test such communications. The problem begins when i try to post a new "operation schedule", swagger shows me the following output:
Error converting value "string" to type 'System.TimeSpan'
In my first attemption to resolve this problem i'd tryied to use Microsoft.AspNetCore.Mvc.NewtonsoftJson, and configured it in startup this way: services.AddMvc().AddNewtonsoftJson();
As long it does not resolved my problem i was thinking in map the types string and TimeSpan on Automapper by doing something like this:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
// Custom types mapping
CreateMap<string, TimeSpan>.ConvertUsing<StringToTimeSpanConverter>();
// OperationSchedule mapping
CreateMap<OperationSchedule, OperationScheduleModel>();
CreateMap<OperationScheduleModel, OperationSchedule>();
}
}
But VisualStudio isn't letting me do so, saying that a cannot use "StringToTimeSpanConverter" as a generic property, something like ConvertUsing(new StringToTimeSpanConverter()); doesn't make any difference.
I really can't even imagine another way to resolve it, but may i missing something? Help please
Error converting value "string" to type 'System.TimeSpan'
The reason for this is that System.Text.Json doesn't support TimeSpan.
One solution is to go back to JSON.NET. Here are the steps:
Add a package reference to Microsoft.AspNetCore.Mvc.NewtonsoftJson.
Update Startup.ConfigureServices to call AddNewtonsoftJson.
services.AddControllers()
.AddNewtonsoftJson();
Another option is to use a custom converter for that TimeSpan:
public class CustomConverter : JsonConverter<TimeSpan>
{
public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var value = reader.GetString();
return TimeSpan.Parse(value);
}
public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
}
Register it in Startup.ConfigureServices with AddJsonOptions:
services.AddControllers()
.AddJsonOptions(options =>
options.JsonSerializerOptions.Converters.Add(new CustomConverter())
);
Result:

Date Only Attribute in .NET Core 3.1

I am migrating a C# API from .NET Framework to .NET Core 3.1.
I have a requirement that some fields return yyyyMMdd only (no time)
and other fields that would return the full DateTime Value (Date and Time).
In the old .NET Framework world, we could make a quick converter like this:
public class OnlyDateConverter : IsoDateTimeConverter
{
public OnlyDateConverter()
{
DateTimeFormat = "yyyyMMdd";
}
}
and use it in my model like
[JsonConverter(typeof(DateTimeConverter))]
public DateTime OrderDate { get; set; }
That isn't working in .NET Core 3.1.
When I call it via Swagger, my JSON that is returned is:
"OrderDate": "2002-05-22T00:00:00"
I know you can add a JsonSerializerOption in Startup.cs, however that will force all dates to use the same formatting. I need to pick and choose.
I have tried:
making multiple json converters, however they never get called/work
[DataType(DataType.Date)]
[JsonConverter(typeof(DateTimeConverter))]
I have spent all day on this. I'm hoping someone has done this and can point out my silly mistake.
This code work for me
in your output model add this :
[DataType(DataType.Date)]
[JsonConverter(typeof(JsonDateConverterExtension))]
public DateTime? DateOfBirth { get; set; }
where JsonDateConverterExtension is :
public class JsonDateConverterExtension : JsonConverter<DateTime?>
{
public override DateTime? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
=> DateTime.ParseExact(reader.GetString(),
"yyyy-MM-dd", CultureInfo.InvariantCulture);
public override void Write(Utf8JsonWriter writer, DateTime? value, JsonSerializerOptions options)
=> writer.WriteStringValue(value?.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
}

The JSON value could not be converted to System.Nullable[System.Int32]

I updated an ASP.NET Core 2.2 API to ASP.NET Core 3.0 and I am using System.Json:
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
.AddJsonOptions(x => {})
I then tried to post JSON data using Angular 8, which was working before:
{
"name": "John"
"userId": "1"
}
The model in the ASP.NET Core 3.0 API is:
public class UserModel {
public String Name { get; set; }
public Int32? UserId { get; set; }
}
And the API Controller action is as follows:
[HttpPost("users")]
public async Task<IActionResult> Create([FromBody]PostModel) {
}
When I submit the model I get the following error:
The JSON value could not be converted to System.Nullable[System.Int32].
Do I need to do something else when using System.Json instead of Newtonsoft?
Microsoft has removed Json.NET dependency from ASP.NET Core 3.0 onwards and using System.Text.Json namespace now for serialization, deserialization and more.
You can still configure your application to use Newtonsoft.Json. For this -
Install Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package
In ConfigureServices() add a call to AddNewtonsoftJson()-
services.AddControllers().AddNewtonsoftJson();
Read more on https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-apis/
https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to
Here Via the json you pass a string value for UserId but your model refer a int32? value for UserId. Then how your value convert from string to int32?
I faced this issue! and all those solutions did not work for me! so I did the following:-
First of all the data returned to me as the following:-
I need to make year as an int and also want to make value as double!
You should make custom JsonConverter, it worked for me after a lot of search, and here is sample of:-
StringToDoubleConverter
public sealed class StringToDoubleConverter : JsonConverter<double>
{
public override double Read(
ref Utf8JsonReader reader,
Type typeToConvert,
JsonSerializerOptions options)
{
double.TryParse(reader.GetString(),out double value);
return value;
}
public override void Write(
Utf8JsonWriter writer,
double value,
JsonSerializerOptions options)
{
throw new NotImplementedException();
}
}
Then you can save it to db! play around as you like
In my scenario I was just sending the "", not the null :)

Json.NET deserializing DateTimeOffset value fails for DateTimeOffset.MinValue without timezone

In my ASP.NET Core Web-API project, I'm getting a HTTP POST call to one of my API controllers.
While evaluating the JSON payload and deserializing its contents, Json.NET stumbles upon a DateTime value of 0001-01-01T00:00:00 and can't convert it to a DateTimeOffset property.
I notice that the value propably should represent the value of DateTimeOffset.MinValue, but its lack of a timezone seems to trip the deserializer up. I can only imagine that the DateTimeOffset.Parse tries to translate it to the hosts current timezone, which results in an underflow of DateTimeOffset.MinValue.
The property is pretty simplistic:
[JsonProperty("revisedDate", NullValueHandling = NullValueHandling.Ignore)]
public DateTimeOffset? RevisedDate { get; set; }
And here is the response sent to the client:
{
"resource.revisedDate": [
"Could not convert string to DateTimeOffset: 0001-01-01T00:00:00. Path 'resource.revisedDate', line 20, position 44."
]
}
I'm using Newtonsoft.Json v11.0.2 and currently am in UTC + 2 (Germany). The exception traceback and error message are here: https://pastebin.com/gX9R9wq0.
I can't fix the calling code, so I have to fix it on my side of the line.
But the question is: How?
The problem seems reproducible only when the machine's time zone TimeZoneInfo.Local has a positive offset from UTC, e.g. (UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna. I was unable to reproduce it in time zones with a non-positive offset such as UTC-05:00 or UTC itself.
Specifically, in JsonReader.ReadDateTimeOffsetString() a call is made to DateTimeOffset.TryParse using DateTimeStyles.RoundtripKind:
if (DateTimeOffset.TryParse(s, Culture, DateTimeStyles.RoundtripKind, out dt))
{
SetToken(JsonToken.Date, dt, false);
return dt;
}
This apparently causes an underflow error in time zones with a positive UTC offset. If in the debugger I parse using DateTimeStyles.AssumeUniversal instead, the problem is avoided.
You might want to report an issue about this to Newtonsoft. The fact that deserialization of a specific DateTimeOffset string fails only when the computer's time zone has certain values seems wrong.
The workaround is to use IsoDateTimeConverter to deserialize your DateTimeOffset properties with IsoDateTimeConverter.DateTimeStyles set to DateTimeStyles.AssumeUniversal. In addition it is necessary to disable the automatic DateTime recognition built into JsonReader by setting JsonReader.DateParseHandling = DateParseHandling.None, which must be done before the reader begins to parse the value for your DateTimeOffset properties.
First, define the following JsonConverter:
public class FixedIsoDateTimeOffsetConverter : IsoDateTimeConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(DateTimeOffset) || objectType == typeof(DateTimeOffset?);
}
public FixedIsoDateTimeOffsetConverter() : base()
{
this.DateTimeStyles = DateTimeStyles.AssumeUniversal;
}
}
Now, if you can modify the JsonSerializerSettings for your controller, use the following settings:
var settings = new JsonSerializerSettings
{
DateParseHandling = DateParseHandling.None,
Converters = { new FixedIsoDateTimeOffsetConverter() },
};
If you cannot easily modify your controller's JsonSerializerSettings you will need to grab DateParseHandlingConverter from this answer to How to prevent a single object property from being converted to a DateTime when it is a string and apply it as well as FixedIsoDateTimeOffsetConverter to your model as follows:
[JsonConverter(typeof(DateParseHandlingConverter), DateParseHandling.None)]
public class RootObject
{
[JsonProperty("revisedDate", NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(FixedIsoDateTimeOffsetConverter))]
public DateTimeOffset? RevisedDate { get; set; }
}
DateParseHandlingConverter must be applied to the model itself rather than the RevisedDate property because the JsonReader will already have recognized 0001-01-01T00:00:00 as a DateTime before the call to FixedIsoDateTimeOffsetConverter.ReadJson() is made.
Update
In comments, #RenéSchindhelm writes, I created an issue to let Newtonsoft know. It is Deserialization of DateTimeOffset value fails depending on system's timezone #1731.
This is what I am using to fix the issue in .NET Core 3.
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.MetadataPropertyHandling = MetadataPropertyHandling.Ignore;
options.SerializerSettings.DateParseHandling = DateParseHandling.None;
options.SerializerSettings.Converters.Add(new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal });
});
...
Change DateTimeOffset to DateTime solved the problem.
Check your Json.NET version and then your input value and formatting. I'm trying the following example and it is working fine for me:
void Main()
{
var json = #"{""offset"":""0001-01-01T00:00:00""}";
var ds = Newtonsoft.Json.JsonConvert.DeserializeObject<TestDS>(json);
Console.WriteLine(ds);
}
public class TestDS {
[Newtonsoft.Json.JsonProperty("offset", NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public DateTimeOffset? DSOffset { get; set; }
}
Here is the output:
DSOffset 1/1/0001 12:00:00 AM -06:00

Categories