Merge pull request #377 from BrandonLWhite/bw-JsonConverterArgs
Implemented support for passing arguments to parameterized JsonConverter constructors
This commit is contained in:
@@ -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));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user