diff --git a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs index 9b647457..3a603d5d 100644 --- a/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs +++ b/Src/Newtonsoft.Json.Tests/JsonConvertTest.cs @@ -1058,5 +1058,149 @@ namespace Newtonsoft.Json.Tests { public Nest A { get; set; } } + + [Test(Description = "Verifies that parameterized JsonConverter constructors are invoked when 'params' are used in JsonConverterAttribute.")] + public void ParametersPassedToJsonConverterConstructor() + { + ClobberMyProperties clobber = new ClobberMyProperties { One = "Red", Two = "Green", Three = "Yellow", Four = "Black" }; + string json = JsonConvert.SerializeObject(clobber); + + Assert.AreEqual("{\"One\":\"Uno-1-Red\",\"Two\":\"Dos-2-Green\",\"Three\":\"Tres-1337-Yellow\",\"Four\":\"Black\"}", json); + } + + public class ClobberMyProperties + { + [JsonConverter(typeof(ClobberingJsonConverter), "Uno", 1)] + public string One { get; set; } + + [JsonConverter(typeof(ClobberingJsonConverter), "Dos", 2)] + public string Two { get; set; } + + [JsonConverter(typeof(ClobberingJsonConverter), "Tres")] + public string Three { get; set; } + + public string Four { get; set; } + } + + public class ClobberingJsonConverter : JsonConverter + { + public string ClobberValueString { get; private set; } + + public int ClobberValueInt { get; private set; } + + public ClobberingJsonConverter(string clobberValueString, int clobberValueInt) + { + ClobberValueString = clobberValueString; + ClobberValueInt = clobberValueInt; + } + + public ClobberingJsonConverter(string clobberValueString) + : this(clobberValueString, 1337) + { + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + writer.WriteValue(ClobberValueString + "-" + ClobberValueInt.ToString() + "-" + value.ToString()); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(string); + } + } + + [Test] + public void WrongParametersPassedToJsonConvertConstructorShouldThrow() + { + IncorrectJsonConvertParameters value = new IncorrectJsonConvertParameters { One = "Boom" }; + + ExceptionAssert.Throws(() => { JsonConvert.SerializeObject(value); }); + } + + public class IncorrectJsonConvertParameters + { + /// + /// We deliberately use the wrong number/type of arguments for ClobberingJsonConverter to ensure an + /// exception is thrown. + /// + [JsonConverter(typeof(ClobberingJsonConverter), "Uno", "Blammo")] + public string One { get; set; } + } + + [Test(Description = "Uses JsonConverter and ItemConverterParameters to demonstrate custom rounding of doubles on a property-by-property basis.")] + public void CustomDoubleRounding() + { + var measurements = new Measurements + { + Loads = new List { 23283.567554707258, 23224.849899771067, 23062.5, 22846.272519910868, 22594.281246368635 }, + Positions = new List { 57.724227689317019, 60.440934405753069, 63.444192925248643, 66.813119113482557, 70.4496501404433 }, + Gain = 12345.67895111213 + }; + + string json = JsonConvert.SerializeObject(measurements); + + + Assert.AreEqual("{\"Positions\":[57.72,60.44,63.44,66.81,70.45],\"Loads\":[23284.0,23225.0,23062.0,22846.0,22594.0],\"Gain\":12345.679}", json); + } + + public class Measurements + { + [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter))] + public List Positions { get; set; } + + [JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] { 0, MidpointRounding.ToEven })] + public List Loads { get; set; } + + [JsonConverter(typeof(RoundingJsonConverter), 4)] + public double Gain { get; set; } + } + + public class RoundingJsonConverter : JsonConverter + { + int _precision; + MidpointRounding _rounding; + + public RoundingJsonConverter() + : this(2) + { + } + + public RoundingJsonConverter(int precision) + : this(precision, MidpointRounding.AwayFromZero) + { + } + + public RoundingJsonConverter(int precision, MidpointRounding rounding) + { + _precision = precision; + _rounding = rounding; + } + + public override bool CanRead + { + get { return false; } + } + + public override bool CanConvert(Type objectType) + { + return objectType == typeof(double); + } + + 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) + { + writer.WriteValue(Math.Round((double)value, _precision, _rounding)); + } + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonContainerAttribute.cs b/Src/Newtonsoft.Json/JsonContainerAttribute.cs index d444189a..de777392 100644 --- a/Src/Newtonsoft.Json/JsonContainerAttribute.cs +++ b/Src/Newtonsoft.Json/JsonContainerAttribute.cs @@ -57,6 +57,17 @@ namespace Newtonsoft.Json /// The collection's items converter. public Type ItemConverterType { get; set; } + /// + /// The parameter list to use when constructing the JsonConverter described by ItemConverterType. + /// If null, the default constructor is used. + /// When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + /// order, and type of these parameters. + /// + /// + /// [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + /// + public object[] ItemConverterParameters { get; set; } + // yuck. can't set nullable properties on an attribute in C# // have to use this approach to get an unset default state internal bool? _isReference; diff --git a/Src/Newtonsoft.Json/JsonConverterAttribute.cs b/Src/Newtonsoft.Json/JsonConverterAttribute.cs index 0b2453cd..f3897801 100644 --- a/Src/Newtonsoft.Json/JsonConverterAttribute.cs +++ b/Src/Newtonsoft.Json/JsonConverterAttribute.cs @@ -46,6 +46,12 @@ namespace Newtonsoft.Json get { return _converterType; } } + /// + /// The parameter list to use when constructing the JsonConverter described by ConverterType. + /// If null, the default constructor is used. + /// + public object[] ConverterParameters { get; private set; } + /// /// Initializes a new instance of the class. /// @@ -57,5 +63,16 @@ namespace Newtonsoft.Json _converterType = converterType; } + + /// + /// Initializes a new instance of the class. + /// + /// Type of the converter. + /// Parameter list to use when constructing the JsonConverter. Can be null. + public JsonConverterAttribute(Type converterType, params object[] converterParameters) + : this(converterType) + { + ConverterParameters = converterParameters; + } } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs index 6ed7c5bf..5175f4c1 100644 --- a/Src/Newtonsoft.Json/JsonPropertyAttribute.cs +++ b/Src/Newtonsoft.Json/JsonPropertyAttribute.cs @@ -53,6 +53,17 @@ namespace Newtonsoft.Json /// The collection's items converter. public Type ItemConverterType { get; set; } + /// + /// The parameter list to use when constructing the JsonConverter described by ItemConverterType. + /// If null, the default constructor is used. + /// When non-null, there must be a constructor defined in the JsonConverter that exactly matches the number, + /// order, and type of these parameters. + /// + /// + /// [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] + /// + public object[] ItemConverterParameters { get; set; } + /// /// Gets or sets the null value handling used when serializing this property. /// diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 2b03edd1..d51bf3f9 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -1269,7 +1269,7 @@ namespace Newtonsoft.Json.Serialization property.ItemIsReference = (propertyAttribute != null) ? propertyAttribute._itemIsReference : null; property.ItemConverter = (propertyAttribute != null && propertyAttribute.ItemConverterType != null) - ? JsonTypeReflector.CreateJsonConverterInstance(propertyAttribute.ItemConverterType) + ? JsonTypeReflector.CreateJsonConverterInstance(propertyAttribute.ItemConverterType, propertyAttribute.ItemConverterParameters) : null; property.ItemReferenceLoopHandling = (propertyAttribute != null) ? propertyAttribute._itemReferenceLoopHandling : null; property.ItemTypeNameHandling = (propertyAttribute != null) ? propertyAttribute._itemTypeNameHandling : null; diff --git a/Src/Newtonsoft.Json/Serialization/JsonContainerContract.cs b/Src/Newtonsoft.Json/Serialization/JsonContainerContract.cs index 0ff731cf..287ecb62 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonContainerContract.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonContainerContract.cs @@ -105,7 +105,8 @@ namespace Newtonsoft.Json.Serialization if (jsonContainerAttribute != null) { if (jsonContainerAttribute.ItemConverterType != null) - ItemConverter = JsonTypeReflector.CreateJsonConverterInstance(jsonContainerAttribute.ItemConverterType); + ItemConverter = JsonTypeReflector.CreateJsonConverterInstance(jsonContainerAttribute.ItemConverterType, + jsonContainerAttribute.ItemConverterParameters); ItemIsReference = jsonContainerAttribute._itemIsReference; ItemReferenceLoopHandling = jsonContainerAttribute._itemReferenceLoopHandling; diff --git a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs index 8f8b0f9d..a2fdf14d 100644 --- a/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs +++ b/Src/Newtonsoft.Json/Serialization/JsonTypeReflector.cs @@ -55,7 +55,8 @@ namespace Newtonsoft.Json.Serialization public const string ShouldSerializePrefix = "ShouldSerialize"; public const string SpecifiedPostfix = "Specified"; - private static readonly ThreadSafeStore> JsonConverterCreatorCache = new ThreadSafeStore>(GetJsonConverterCreator); + private static readonly ThreadSafeStore> JsonConverterCreatorCache = + new ThreadSafeStore>(GetJsonConverterCreator); #if !(NET20 || NETFX_CORE) private static readonly ThreadSafeStore AssociatedMetadataTypesCache = new ThreadSafeStore(GetAssociateMetadataTypeFromAttribute); @@ -152,30 +153,58 @@ namespace Newtonsoft.Json.Serialization if (converterAttribute != null) { - Func creator = JsonConverterCreatorCache.Get(converterAttribute.ConverterType); + Func creator = JsonConverterCreatorCache.Get(converterAttribute.ConverterType); if (creator != null) - return creator(); + return creator(converterAttribute.ConverterParameters); } return null; } - public static JsonConverter CreateJsonConverterInstance(Type converterType) + /// + /// Lookup and create an instance of the JsonConverter type described by the argument. + /// + /// The JsonConverter type to create. + /// Optional arguments to pass to an initializing constructor of the JsonConverter. + /// If null, the default constructor is used. + public static JsonConverter CreateJsonConverterInstance(Type converterType, object[] converterArgs) { - Func converterCreator = JsonConverterCreatorCache.Get(converterType); - return converterCreator(); + Func converterCreator = JsonConverterCreatorCache.Get(converterType); + return converterCreator(converterArgs); } - private static Func GetJsonConverterCreator(Type converterType) + /// + /// Create a factory function that can be used to create instances of a JsonConverter described by the + /// argument type. The returned function can then be used to either invoke the converter's default ctor, or any + /// parameterized constructors by way of an object array. + /// + private static Func GetJsonConverterCreator(Type converterType) { Func defaultConstructor = (ReflectionUtils.HasDefaultConstructor(converterType, false)) ? ReflectionDelegateFactory.CreateDefaultConstructor(converterType) : null; - return () => + return (parameters) => { try { + if (parameters != null) + { + ObjectConstructor parameterizedConstructor = null; + Type[] paramTypes = parameters.Select(param => param.GetType()).ToArray(); + ConstructorInfo parameterizedConstructorInfo = converterType.GetConstructor(paramTypes); + + if (null != parameterizedConstructorInfo) + { + parameterizedConstructor = ReflectionDelegateFactory.CreateParametrizedConstructor(parameterizedConstructorInfo); + return (JsonConverter)parameterizedConstructor(parameters); + } + else + { + throw new JsonException("No matching parameterized constructor found for '{0}'.".FormatWith(CultureInfo.InvariantCulture, converterType)); + } + } + if (defaultConstructor == null) throw new JsonException("No parameterless constructor defined for '{0}'.".FormatWith(CultureInfo.InvariantCulture, converterType));