Merge pull request #377 from BrandonLWhite/bw-JsonConverterArgs

Implemented support for passing arguments to parameterized JsonConverter constructors
This commit is contained in:
James Newton-King
2014-09-23 12:46:38 +12:00
7 changed files with 223 additions and 10 deletions
@@ -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<JsonException>(() => { JsonConvert.SerializeObject(value); });
}
public class IncorrectJsonConvertParameters
{
/// <summary>
/// We deliberately use the wrong number/type of arguments for ClobberingJsonConverter to ensure an
/// exception is thrown.
/// </summary>
[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<double> { 23283.567554707258, 23224.849899771067, 23062.5, 22846.272519910868, 22594.281246368635 },
Positions = new List<double> { 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<double> Positions { get; set; }
[JsonProperty(ItemConverterType = typeof(RoundingJsonConverter), ItemConverterParameters = new object[] { 0, MidpointRounding.ToEven })]
public List<double> 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));
}
}
}
}
@@ -57,6 +57,17 @@ namespace Newtonsoft.Json
/// <value>The collection's items converter.</value>
public Type ItemConverterType { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <example>
/// [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })]
/// </example>
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;
@@ -46,6 +46,12 @@ namespace Newtonsoft.Json
get { return _converterType; }
}
/// <summary>
/// The parameter list to use when constructing the JsonConverter described by ConverterType.
/// If null, the default constructor is used.
/// </summary>
public object[] ConverterParameters { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="JsonConverterAttribute"/> class.
/// </summary>
@@ -57,5 +63,16 @@ namespace Newtonsoft.Json
_converterType = converterType;
}
/// <summary>
/// Initializes a new instance of the <see cref="JsonConverterAttribute"/> class.
/// </summary>
/// <param name="converterType">Type of the converter.</param>
/// <param name="converterParameters">Parameter list to use when constructing the JsonConverter. Can be null.</param>
public JsonConverterAttribute(Type converterType, params object[] converterParameters)
: this(converterType)
{
ConverterParameters = converterParameters;
}
}
}
@@ -53,6 +53,17 @@ namespace Newtonsoft.Json
/// <value>The collection's items converter.</value>
public Type ItemConverterType { get; set; }
/// <summary>
/// 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.
/// </summary>
/// <example>
/// [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })]
/// </example>
public object[] ItemConverterParameters { get; set; }
/// <summary>
/// Gets or sets the null value handling used when serializing this property.
/// </summary>
@@ -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;
@@ -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;
@@ -55,7 +55,8 @@ namespace Newtonsoft.Json.Serialization
public const string ShouldSerializePrefix = "ShouldSerialize";
public const string SpecifiedPostfix = "Specified";
private static readonly ThreadSafeStore<Type, Func<JsonConverter>> JsonConverterCreatorCache = new ThreadSafeStore<Type, Func<JsonConverter>>(GetJsonConverterCreator);
private static readonly ThreadSafeStore<Type, Func<object[], JsonConverter>> JsonConverterCreatorCache =
new ThreadSafeStore<Type, Func<object[], JsonConverter>>(GetJsonConverterCreator);
#if !(NET20 || NETFX_CORE)
private static readonly ThreadSafeStore<Type, Type> AssociatedMetadataTypesCache = new ThreadSafeStore<Type, Type>(GetAssociateMetadataTypeFromAttribute);
@@ -152,30 +153,58 @@ namespace Newtonsoft.Json.Serialization
if (converterAttribute != null)
{
Func<JsonConverter> creator = JsonConverterCreatorCache.Get(converterAttribute.ConverterType);
Func<object[], JsonConverter> creator = JsonConverterCreatorCache.Get(converterAttribute.ConverterType);
if (creator != null)
return creator();
return creator(converterAttribute.ConverterParameters);
}
return null;
}
public static JsonConverter CreateJsonConverterInstance(Type converterType)
/// <summary>
/// Lookup and create an instance of the JsonConverter type described by the argument.
/// </summary>
/// <param name="converterType">The JsonConverter type to create.</param>
/// <param name="converterArgs">Optional arguments to pass to an initializing constructor of the JsonConverter.
/// If null, the default constructor is used.</param>
public static JsonConverter CreateJsonConverterInstance(Type converterType, object[] converterArgs)
{
Func<JsonConverter> converterCreator = JsonConverterCreatorCache.Get(converterType);
return converterCreator();
Func<object[], JsonConverter> converterCreator = JsonConverterCreatorCache.Get(converterType);
return converterCreator(converterArgs);
}
private static Func<JsonConverter> GetJsonConverterCreator(Type converterType)
/// <summary>
/// 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.
/// </summary>
private static Func<object[], JsonConverter> GetJsonConverterCreator(Type converterType)
{
Func<object> defaultConstructor = (ReflectionUtils.HasDefaultConstructor(converterType, false))
? ReflectionDelegateFactory.CreateDefaultConstructor<object>(converterType)
: null;
return () =>
return (parameters) =>
{
try
{
if (parameters != null)
{
ObjectConstructor<object> 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));