Is it possible to customize the way types are serialized to the response in ASP.NET Core MVC?
In my particular use case I've got a struct, AccountId, that simply wraps around a Guid:
public readonly struct AccountId
{
public Guid Value { get; }
// ...
}
When I return it from an action method, unsurprisingly, it serializes to the following:
{ "value": "F6556C1D-1E8A-4D25-AB06-E8E244067D04" }
Instead, I'd like to automatically unwrap the Value so it serializes to a plain string:
"F6556C1D-1E8A-4D25-AB06-E8E244067D04"
Can MVC be configured to achieve this?
You can customize the output produced by JSON.NET with a custom converter.
In your case, it would look like this:
[JsonConverter(typeof(AccountIdConverter))]
public readonly struct AccountId
{
public Guid Value { get; }
// ...
}
public class AccountIdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
=> objectType == typeof(AccountId);
// this converter is only used for serialization, not to deserialize
public override bool CanRead => false;
// implement this if you need to read the string representation to create an AccountId
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
=> throw new NotImplementedException();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (!(value is AccountId accountId))
throw new JsonSerializationException("Expected AccountId object value.");
// custom response
writer.WriteValue(accountId.Value);
}
}
If you prefer not to use the JsonConverter attribute, it's possible to add converters in ConfigureServices (requires Microsoft.AspNetCore.Mvc.Formatters.Json):
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.Converters.Add(new AccountIdConverter());
});
}
Related
I am trying to deserialize a subclass in a .NET 5 api project. The model binder is handing back a null even though my custom JSON converter is being called and is returning the correct object.
So that I can have polymorphic deserialization, I'm using the NewtonSoft serializer, added like so:
services
.AddControllers()
.AddNewtonsoftJson(opts =>
{
opts.SerializerSettings.Converters.Insert(0, new RecordJsonConverter());
});
And I have Microsoft.AspNetCore.Mvc.NewtonsoftJson added to the project.
The classes are like:
public class RecordDTO
{
[...]
}
public class ARecordDTO : RecordDTO
{
[...]
}
The controller accepts the base class:
public async override Task<IActionResult> Post([FromBody] RecordDTO dto)
And the JSON Converter:
public class RecordJsonConverter : JsonConverter
{
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
// Only objects of the base type RecordDTO need to be converted by this custom converter.
// If it is already knows which derived class it is, then we
// can just let it use the standard converter.
var recordType = typeof(RecordDTO);
return recordType.IsAssignableFrom(objectType) && !objectType.IsSubclassOf(recordType);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
[...]
return target;
}
}
When I post an ARecordDTO object, I see ReadJson on my custom RecordJsonConverter being called and filling out an ARecord object and returning it correctly.
But then we're back inside the controller action, and the dto argument is now null instead of being the ARecordDTO that was just created for binding.
Has anyone else encountered this? What would cause the object returned by the JSON converter to be discarded during binding?
Well, in case anyone else shows up here and was similarly uninformed:
If there are any errors present in the ModelState, then the action argument binds as null (rather than just skipping the single field that had the problem and returning the rest of the object, which is the behavior I would've assumed.)
I need to ignore all my null fields in my customized json converter .
My converter inherits from JsonConverter , and i've overrideed the WriteJson method.
I need to configure this setting NullValueHandling.Ignore for my converter ,but i can't see how to make it .
public class CommonJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return true;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
//i dont need it now
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var maclasse = ( Maclasse ) value ;
writer.WritePropertyNameAndValuer("StringValue",maclasse.id);
writer.WritePropertyNameAndValuer("StringValue",maclasse.nom);
}
}
Thnak you All.
Within an dotnet core mvc application one can call AddJsonOptions() after AddMvc() and supply it with the options to ignore null values within your json. However this effects all of your Json results which go through the request pipeline.
The below code can be added to your startup.cs
public void ConfigureServices(IServiceCollection services)
{
// needs a reference in your `csproj` file to `Microsoft.AspNetCore.Mvc.Formatters.Json`
services.AddJsonOptions(Startup.SetJsonOptions);
}
private static void SetJsonOptions(MvcJsonOptions options)
{
options.SerializerSettings.Converters.Add(new CustomConverter());
options.SerializerSettings.NullValueHandling = NullValueHandling.Ignore;
}
For a normal console app there's something like which goes probably somewhere in your main entrypoint (application setup):
JsonConvert.DefaultSettings = () => new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
};
Edited:
You may use NullValueHandling setting over a base class, and inherit other classes from it.
[JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)]
public class BaseClass{}
I have a table with a SqlHierarchyId field type.
The default JSON serialization returns a javascript object that looks like this: {isNull: false} instead of a string like this /1/
Is there some way to set the serialization properly?
public class MyClass
{
[SerializeToString]
public SqlHierarchyId NodeId { get; set; }
}
According to the MSDN documentation, SqlHierarchyId implements both a ToString() method and a static Parse() method to facilitate converting it to a canonical string representation and back. So, assuming you are using Json.Net for serialization, you should be able to make a straightforward JsonConverter class to bridge the gap. Here is what it would look like:
public class SqlHierarchyIdConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(SqlHierarchyId));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string id = (string)reader.Value;
return (id == null || id == SqlHierarchyId.Null.ToString()) ? SqlHierarchyId.Null : SqlHierarchyId.Parse(id);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
writer.WriteValue(value.ToString());
}
}
To use it, you can either mark the SqlHierarchyId properties in your classes with a [JsonConverter] attribute like this:
class MyClass
{
[JsonConverter(typeof(SqlHierarchyIdConverter))]
public SqlHierarchyId NodeId { get; set; }
}
Or, alternatively, you can pass an instance of the converter as a parameter to SerializeObject or DeserializeObject:
string json = JsonConvert.SerializeObject(myClass, new SqlHierarchyIdConverter());
Here is a round-trip demo: https://dotnetfiddle.net/7h7E82
We have upgraded Newtonsoft.Json from 10.0.3 to 11.0.1 and code that has been working previously does not work anymore.
We have following JSON:
[{"mail-type":"registration","response":"250 OK id=1UjnNr-gdf-C0 ","invoice-id":,"email":"testuser08#test.com"}]
and we call following method:
var events = JsonConvert.DeserializeObject<Event[]>(jsonEvents);
This worked fine on 10.0.3, but does not on 11.0.1. On this version following exception is thrown:
Exception thrown: 'Newtonsoft.Json.JsonReaderException' in
Newtonsoft.Json.dll
Additional information: An undefined token is not a valid
System.Nullable`1[System.Int64]. Path '[0].invoice-id', line 1.
I tried to pass following options to DeserializeObject
var settings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Ignore
};
But still the same error. What changes have to be done to make it work on 11.0.1. I am affraid we cannot accomodate JSON output as this comes from third party API.
Your JSON sample is not well-formed. If I upload your JSON to https://jsonlint.com/ then the following error is generated:
Error: Parse error on line 4:
...0 ", "invoice-id": , "email": "testuse
----------------------^
Expecting 'STRING', 'NUMBER', 'NULL', 'TRUE', 'FALSE', '{', '[', got ','
The line in question is as follows:
"invoice-id": ,
According to the JSON standard there needs to be a value between the : and the ,. But, why did this work in Json.NET 10.0? Apparently it was a bug, which was fixed. According to the 11.0.1 release notes:
Fix - Fixed not erroring when reading undefined for nullable long
So if we assume that your Event type looks like this:
public partial class Event
{
[JsonProperty("invoice-id")]
public long? InvoiceId { get; set; }
// Other properties as required
}
Then in 10.0 your JSON could be deserialized successfully using this type, but in 11.0 it cannot. If, however, we change InvoiceId to be an int?:
public partial class Event
{
[JsonProperty("invoice-id")]
public int? InvoiceId { get; set; }
// Other properties as required
}
It fails in both versions. Thus the fix appears to have been to handle int? and long? consistently.
Ideally, you should ask whoever sent you such JSON to fix it so that it is well-formed as defined by http://www.json.org/ and RFC 8259. If you nevertheless need to parse such JSON in the same manner as Json.NET 10.0, you could introduce TolerantNullableLongConverter as follows:
public class TolerantNullableLongConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(long?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType == JsonToken.Undefined)
return null;
if (reader.Value is long)
return reader.Value;
// string or int or decimal or ...
return serializer.Deserialize<long>(reader);
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
}
And apply it to your type as follows:
public partial class Event
{
[JsonProperty("invoice-id")]
[JsonConverter(typeof(TolerantNullableLongConverter))]
public long? InvoiceId { get; set; }
// Other properties as required
}
You can implement workaround with custom converter:
internal class NullableLongFixupConverter : JsonConverter {
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) {
writer.WriteValue(value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) {
// if token undefined - return null
if (reader.TokenType == JsonToken.Undefined)
return null;
// otherwise - value
return (long?) reader.Value;
}
public override bool CanConvert(Type objectType) {
return objectType == typeof(long?);
}
}
Then you can either decorate properties with it:
class Event {
[JsonProperty("invoice-id")]
[JsonConverter(typeof(NullableLongFixupConverter))]
public long? InvoiceId { get; set; }
}
or register globally (will be invoked only for properties of type long?):
JsonConvert.DefaultSettings = () =>
{
var s = new JsonSerializerSettings();
s.Converters.Add(new NullableLongFixupConverter());
return s;
};
I'm using Json.NET to serialize validation data for data field. On the .NET side, validation data is a list of ValidationAttribute objects. However, I'd like to serialize them in a special form like this:
[
{ Type: 'Required', ErrorMessage: '{FieldName} is required' },
{ Type: 'RegularExpression', Pattern: '^\d+$', ErrorMessage: '...'
]
In an ideal solution I could simply intercept the object before serialization and, I could create a corresponding Dictionary<string, object> object to serialize instead of the original one.
Are there any solutions for this scenario?
You can implement your own JsonConverter class and convert your collection as you wish.
You just need to create you class and inherit it from JsonConverter
public class YourSerializer : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
throw new NotImplementedException();
}
public override bool CanConvert(Type objectType)
{
return typeof(YourClassName).IsAssignableFrom(objectType);
}
}
and then you need to decorate your class which will be serialized with the attribute (looks like it's not what you want)
[JsonConverter(typeof(YourSerializer))]
public class YourClassName
{
public string Name { get; set; }
public string Value { get; set; }
}
or, pass an instance of your serializer to Serialize methos:
string json = JsonConvert.SerializeObject(sourceObj, Formatting.Indented, new YourSerializer(typeof(yourClassName)));
Here is a few links:
http://www.newtonsoft.com/json/help/html/CustomJsonConverter.htm
http://blog.maskalik.com/asp-net/json-net-implement-custom-serialization/
Hope, it will help.