You can apply converters to specific properties by using a custom IContractResolver
inheriting from DefaultContractResolver
.
First, grab ConfigurableContractResolver
from this answer to How to add metadata to describe which properties are dates in JSON.Net:
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, create a method to configure the JsonObjectContract
for Person
as follows:
public static class JsonContractExtensions
{
public static void ConfigurePerson(this JsonContract contract)
{
if (!typeof(Person).IsAssignableFrom(contract.UnderlyingType))
return;
var objectContract = contract as JsonObjectContract;
if (objectContract == null)
return;
var property = objectContract.Properties.Where(p => p.UnderlyingName == nameof(Person.LastName)).Single();
property.Converter = new AllCapsConverter();
}
}
And finally serialize as follows:
// Cache the contract resolver statically for best performance.
var resolver = new ConfigurableContractResolver()
.Configure((s, e) => { e.Contract.ConfigurePerson(); });
var settigs = new JsonSerializerSettings
{
ContractResolver = resolver,
};
var person = new Person
{
FirstName = "George",
LastName = "Washington"
};
var serialized = JsonConvert.SerializeObject(person, settigs);
Notes:
Rather than creating ConfigurableContractResolver
it would have been possible to subclass DefaultContractResolver
, override DefaultContractResolver.CreateProperty
, and hardcode the necessary logic for Person.LastName
there. Creating a configurable resolver that allows for customizations to be combined in run time seems more useful and reuseable, however.
In AllCapsConverter.WriteJson()
it would be simpler to use writer.WriteValue(string)
to write your uppercase string:
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var upper = ((string)value).ToUpperInvariant();
writer.WriteValue(upper);
}
You may want to cache the contract resolver for best performance.
Sample fiddle here.