using System.Collections;
using System.Collections.Generic;
using System.Runtime.Serialization.Formatters;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization;
[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;
public static class StringSanitizeOptionsExtensions
public static bool HasFlag(this Option options, Option flag)
return (options & flag) == flag;
public class StringSanitizingConverter : JsonConverter
public StringSanitizingConverter() : this(Option.Default) { }
public StringSanitizingConverter(Option 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();
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
var text = (string)value;
if (!options.HasFlag(Option.DoNotTrim))
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>()
property.Converter = property.MemberConverter = new StringSanitizingConverter(attr.StringSanitizeOptions);
public class ConfigurableContractResolver : DefaultContractResolver
readonly object contractCreatedPadlock = new object();
event EventHandler<ContractCreatedEventArgs> contractCreated;
void OnContractCreated(JsonContract contract, Type objectType)
EventHandler<ContractCreatedEventArgs> created;
lock (contractCreatedPadlock)
created = contractCreated;
created(this, new ContractCreatedEventArgs(contract, objectType));
public event EventHandler<ContractCreatedEventArgs> ContractCreated
lock (contractCreatedPadlock)
throw new InvalidOperationException("ContractCreated events cannot be added after the first contract is generated.");
contractCreated += value;
lock (contractCreatedPadlock)
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);
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;
public static void Test()
var input = new Candidate
CandidateName = " My Name ",
DefaultString = " My Default String ",
StringLiteral = " My String Literal ",
DefaultStrings = new List<string> { " a ", " B ", " c " },
var json = JsonConvert.SerializeObject(input, Formatting.Indented);
Console.WriteLine("\nInput example: ");
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);
Console.WriteLine("\nDe-serialized and re-serialized {0}", candidate);
Console.WriteLine(json2);
Assert.IsTrue(candidate.CandidateName == JToken.Parse(json2)["CandidateName"].ToString().Trim().ToLowerInvariant());
Assert.IsTrue(candidate.DefaultString == JToken.Parse(json2)["DefaultString"].ToString().Trim());
Assert.IsTrue(candidate.StringLiteral == JToken.Parse(json2)["StringLiteral"].ToString());
Assert.IsTrue(candidate.DefaultStrings.SequenceEqual(JToken.Parse(json2)["DefaultStrings"].ToObject<List<string>>().Select(s => s.Trim())));
public static void Main()
Console.WriteLine("Environment version: " + Environment.Version);
Console.WriteLine("Json.NET version: " + typeof(JsonSerializer).Assembly.FullName);
Console.WriteLine("Failed with unhandled exception: ");
public class AssertionFailedException : System.Exception
public AssertionFailedException() : base() { }
public AssertionFailedException(string s) : base(s) { }
public static class Assert
public static void IsTrue(bool value)
public static void IsTrue(bool value, string message)
throw new AssertionFailedException(message ?? "failed");