How to Set Custom JSONserializersettings for JSON.Net in ASP.NET Web API

How to set custom JsonSerializerSettings for Json.NET in ASP.NET Web API?

You can customize the JsonSerializerSettings by using the Formatters.JsonFormatter.SerializerSettings property in the HttpConfiguration object.

For example, you could do that in the Application_Start() method:

protected void Application_Start()
{
HttpConfiguration config = GlobalConfiguration.Configuration;
config.Formatters.JsonFormatter.SerializerSettings.Formatting =
Newtonsoft.Json.Formatting.Indented;
}

Web API: Configure JSON serializer settings on action or controller level

Option 1 (quickest)

At action level you may always use a custom JsonSerializerSettings instance while using Json method:

public class MyController : ApiController
{
public IHttpActionResult Get()
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var model = new MyModel();
return Json(model, settings);
}
}

Option 2 (controller level)

You may create a new IControllerConfiguration attribute which customizes the JsonFormatter:

public class CustomJsonAttribute : Attribute, IControllerConfiguration 
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
var formatter = controllerSettings.Formatters.JsonFormatter;

controllerSettings.Formatters.Remove(formatter);

formatter = new JsonMediaTypeFormatter
{
SerializerSettings =
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
}
};

controllerSettings.Formatters.Insert(0, formatter);
}
}

[CustomJson]
public class MyController : ApiController
{
public IHttpActionResult Get()
{
var model = new MyModel();
return Ok(model);
}
}

How to set json serializer settings in asp.net core 3?

AddMvc returns an IMvcBuilder implementation, which has a corresponding AddJsonOptions extension method. The new-style methods AddControllers, AddControllersWithViews, and AddRazorPages also return an IMvcBuilder implementation. Chain with these in the same way you would chain with AddMvc:

services.AddControllers()
.AddJsonOptions(options =>
{
// ...
});

Note that options here is no longer for Json.NET, but for the newer System.Text.Json APIs. If you still want to use Json.NET, see tymtam's answer

ASP.NET Core API JSON serializersettings per request

Thanks for the comments and answers. I found a solution with Input and outputformatters. With thanks to http://rovani.net/Explicit-Model-Constructor/ to point me in the right direction.

I've created my own input and outputformatters, which inherit from JsonInputFormatter to keep as much functionality the same.

In the constructor I set the supported mediatype (used some that looks like the existing one for JSON).

Also must override CreateJsonSerializer to set the ContractResolver to the desired one (could implement singleton).

Must do it this way, because changing the serializerSettings in the constructor would change the serializersettings for all input/outputformatters, meaning the default JSON formatters will also use the new contract resolver.

Also doing it this way means you can setup some default JSON options via AddMvc().AddJsonOption()

Example inputformatter, outputformatter uses the same principle:

static MediaTypeHeaderValue protoMediaType = MediaTypeHeaderValue.Parse("application/jsonfull");

public JsonFullInputFormatter(ILogger logger, JsonSerializerSettings serializerSettings, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider)
: base(logger, serializerSettings, charPool, objectPoolProvider)
{
this.SupportedMediaTypes.Clear();
this.SupportedMediaTypes.Add(protoMediaType);
}

protected override JsonSerializer CreateJsonSerializer()
{
var serializer = base.CreateJsonSerializer();
serializer.ContractResolver = new NoJsonPropertyNameContractResolver();

return serializer;
}

As per the mentioned URL above the setup class:

public class YourMvcOptionsSetup : IConfigureOptions<MvcOptions>
{
private readonly ILoggerFactory _loggerFactory;
private readonly JsonSerializerSettings _jsonSerializerSettings;
private readonly ArrayPool<char> _charPool;
private readonly ObjectPoolProvider _objectPoolProvider;

public YourMvcOptionsSetup(ILoggerFactory loggerFactory, IOptions<MvcJsonOptions> jsonOptions, ArrayPool<char> charPool, ObjectPoolProvider objectPoolProvider)
{
//Validate parameters and set fields
}

public void Configure(MvcOptions options)
{
var jsonFullInputFormatter = new JsonFullInputFormatter(
_loggerFactory.CreateLogger<JsonFullInputFormatter>(),
_jsonSerializerSettings,
_charPool,
_objectPoolProvider
);

options.InputFormatters.Add(jsonFullInputFormatter);

options.OutputFormatters.Add(new JsonFullOutputFormatter(
_jsonSerializerSettings,
_charPool
));
}

And then an extension method to register it:

public static class MvcBuilderExtensions
{
public static IMvcBuilder AddJsonFullFormatters(this IMvcBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
ServiceDescriptor descriptor = ServiceDescriptor.Transient<IConfigureOptions<MvcOptions>, YourMvcOptionsSetup>();
builder.Services.TryAddEnumerable(descriptor);
return builder;
}
}

Call it in ConfigureServices:

services.AddMvc(config =>
{
config.RespectBrowserAcceptHeader = true; // To use the JsonFullFormatters if clients asks about it via Accept Header
})
.AddJsonFullFormatters() //Add our own JSON Formatters
.AddJsonOptions(opt =>
{
//Set up some default options all JSON formatters must use (if any)
});

Now our Xamarin App can access the webapi and receive JSON with (short) property names set via JsonProperty attribute.

And in the website we can get the full JSON property names by adding an Accept (get calls) and ContentType (post/put calls) header. Which we do once via jQuery's $.ajaxSetup(.

$.ajaxSetup({
contentType: "application/jsonfull; charset=utf-8",
headers: { 'Accept': 'application/jsonfull' }
});

C# WebAPI: Set default JSON serializer to NewtonSoft JSON

var formatter = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
formatter.SerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
TypeNameHandling = TypeNameHandling.Objects,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};

Would be placed in your global.asax

Access custom attributes of .NET class inside custom json converter

The stack of containing object(s) is not made available to JsonConverter.ReadJson(), thus you cannot do what you want inside ReadJson().

Instead, what you can do is to create a custom contract resolver that applies an appropriately configured instance of StringSanitizingConverter based on the properties of the object for which a contract is being generated.

First, let's say your data model, attribute, and JsonConverter look like the following (where I had to modify a few things to make your code compile and include some additional test cases):

public class Candidate
{
[StringSanitizingOptions(Option.ToLowerCase)]
public string CandidateName { get; set; }

[StringSanitizingOptions(Option.DoNotTrim)]
public string StringLiteral { get; set; }

public string DefaultString { get; set; }

public List<string> DefaultStrings { get; set; }
}

[System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field | System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public class StringSanitizingOptionsAttribute : System.Attribute
{
public Option StringSanitizeOptions { get; set; }

public StringSanitizingOptionsAttribute(Option stringSanitizeOptions)
{
this.StringSanitizeOptions = stringSanitizeOptions;
}
}

[Flags]
public enum Option
{
Default = 0,
ToLowerCase = (1<<0),
DoNotTrim = (1<<1),
}

public static class StringSanitizeOptionsExtensions
{
public static bool HasFlag(this Option options, Option flag)
{
return (options & flag) == flag;
}
}

public class StringSanitizingConverter : JsonConverter
{
readonly Option options;

public StringSanitizingConverter() : this(Option.Default) { }

public StringSanitizingConverter(Option options)
{
this.options = options;
}

public override bool CanConvert(Type objectType)
{
return objectType == typeof(string);
}

public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
if (reader.Value != null)
{
var sanitizedString = (reader.Value as string);

if (!options.HasFlag(Option.DoNotTrim))
sanitizedString = sanitizedString.Trim();

if (options.HasFlag(Option.ToLowerCase))
sanitizedString = sanitizedString.ToLowerInvariant();

return sanitizedString;
}

return reader.Value;
}

public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
// WriteJson is never called with null
var text = (string)value;

if (!options.HasFlag(Option.DoNotTrim))
text = text.Trim();

writer.WriteValue(text);
}
}

Next, grab ConfigurableContractResolver from How to add metadata to describe which properties are dates in JSON.Net, and define the extension method JsonContractExtensions.AddStringConverters():

public static class JsonContractExtensions
{
public static JsonContract AddStringConverters(this JsonContract contract)
{
if (contract is JsonPrimitiveContract)
{
if (contract.UnderlyingType == typeof(string))
contract.Converter = new StringSanitizingConverter();
}
else if (contract is JsonObjectContract)
{
var objectContract = (JsonObjectContract)contract;
foreach (var property in objectContract.Properties)
{
if (property.PropertyType == typeof(string))
{
var attr = property.AttributeProvider.GetAttributes(typeof(StringSanitizingOptionsAttribute), true)
.Cast<StringSanitizingOptionsAttribute>()
.SingleOrDefault();
if (attr != null)
{
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
}
}
}
}
return contract;
}
}

public class ConfigurableContractResolver : DefaultContractResolver
{
// This contract resolver taken from the answer to
// https://stackoverflow.com/questions/46047308/how-to-add-metadata-to-describe-which-properties-are-dates-in-json-net
// https://stackoverflow.com/a/46083201/3744182

readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
int contractCount = 0;

void OnContractCreated(JsonContract contract, Type objectType)
{
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
{
contractCount++;
created = contractCreated;
}
if (created != null)
{
created(this, new ContractCreatedEventArgs(contract, objectType));
}
}

public event EventHandler<ContractCreatedEventArgs> ContractCreated
{
add
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
}
contractCreated += value;
}
}
remove
{
lock (contractCreatedPadlock)
{
if (contractCount > 0)
{
throw new InvalidOperationException("ContractCreated events cannot be removed after the first contract is generated.");
}
contractCreated -= value;
}
}
}

protected override JsonContract CreateContract(Type objectType)
{
var contract = base.CreateContract(objectType);
OnContractCreated(contract, objectType);
return contract;
}
}

public class ContractCreatedEventArgs : EventArgs
{
public JsonContract Contract { get; private set; }
public Type ObjectType { get; private set; }

public ContractCreatedEventArgs(JsonContract contract, Type objectType)
{
this.Contract = contract;
this.ObjectType = objectType;
}
}

public static class ConfigurableContractResolverExtensions
{
public static ConfigurableContractResolver Configure(this ConfigurableContractResolver resolver, EventHandler<ContractCreatedEventArgs> handler)
{
if (resolver == null || handler == null)
throw new ArgumentNullException();
resolver.ContractCreated += handler;
return resolver;
}
}

Then, finally you can deserialize and serialize Candidate as follows:

var settings = new JsonSerializerSettings
{
ContractResolver = new ConfigurableContractResolver
{
}.Configure((s, e) => { e.Contract.AddStringConverters(); }),
};

var candidate = JsonConvert.DeserializeObject<Candidate>(json, settings);

var json2 = JsonConvert.SerializeObject(candidate, Formatting.Indented, settings);

Notes:

  1. I don't know why the stack of containing object(s) is not available in ReadJson(). Possibilities include:

    • Simplicity.
    • A JSON object is "an unordered set of name/value pairs", so trying to access the containing .Net object while reading a property value isn't guaranteed to work, since the information required might not have been read in yet (and the parent might not even have been constructed).
  2. Because a default instance of StringSanitizingConverter is applied to the contract generated for string itself, it is not necessary to add the converter to JsonSerializer.SettingsConverters. This in turn may lead to a small performance enhancement as CanConvert will no longer get called.

  3. JsonProperty.MemberConverter was recently marked obsolete in Json.NET 11.0.1 but must be set to the same value as JsonProperty.Converter in previous versions of Json.NET. If you are using 11.0.1 or a more recent version you should be able to remove the setting.

  4. You may want to cache the contract resolver for best performance.

  5. To modify JsonSerializerSettings in asp.net-web-api, see JsonSerializerSettings and Asp.Net Core, Web API: Configure JSON serializer settings on action or controller level, How to set custom JsonSerializerSettings for Json.NET in MVC 4 Web API? or ASP.NET Core API JSON serializersettings per request, depending on your requirements and the version of the framework in use.

Sample working .Net fiddle here.

How to set Json.NET ContractSerializer for a certain specific type instead of globally?

I ended up using a JsonConverter that only writes the parameters that are specified in the "properties" list. It's more low level than a ContractResolver or a formatter, but I don't think it's possible to configure either one for a specific type.

public class ResourceConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Resource));
}

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)
{
List<string> properties = new List<string>(new string[] { "Name", "Label" });

writer.WriteStartObject();
foreach (MemberInfo mi in value.GetType().GetMembers(BindingFlags.GetField | BindingFlags.Instance | BindingFlags.Public) )
{
PropertyInfo p = mi as PropertyInfo;

if (p != null && p.GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length == 0 && properties.Contains(p.Name))
{
writer.WritePropertyName(p.Name);
serializer.Serialize(writer, p.GetValue(value, new object[] { }));
}
}
writer.WriteEndObject();
}
}

This can be applied to a class using the attribute:

[JsonConverter(typeof(ResourceConverter))]

This seems like a hack though, I think I should use the contract resolver to get the list of properties to serialize instead of using reflection directly, but I'm not sure how.



Related Topics



Leave a reply



Submit