Open TylerBrinkley opened 7 years ago
With the current version of Json.NET I was able to implement this same thing using the custom contract resolver below. But I do think this should be implemented directly within Json.NET since it isn't a breaking change as the existing behavior throws an exception and the feature can be very useful.
public sealed class CustomContractResolver : DefaultContractResolver
{
protected override JsonConverter ResolveContractConverter(Type objectType)
{
var typeInfo = objectType.GetTypeInfo();
if (typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition)
{
var jsonConverterAttribute = typeInfo.GetCustomAttribute<JsonConverterAttribute>();
if (jsonConverterAttribute != null && jsonConverterAttribute.ConverterType.GetTypeInfo().IsGenericTypeDefinition)
{
return (JsonConverter)Activator.CreateInstance(jsonConverterAttribute.ConverterType.MakeGenericType(typeInfo.GenericTypeArguments), jsonConverterAttribute.ConverterParameters);
}
}
return base.ResolveContractConverter(objectType);
}
}
There are a lot of problems and questions I can see.
Thanks for thinking through this. Those are great points.
IEnumerable<T>
. Then retrieve the T and get it's generic arguments?MakeGenericType
method which will throw the exceptions if the provided generic arguments don't work with the converter type.I just pushed another commit to the PR to support ItemsConverterType by the method I proposed above.
I spent a ton of time looking at this today and I'm still not sure about it. Is this something people would actually find useful? What problems is it solving? No one has ever mentioned it before and I'm really really not a fan of adding features just for the hell of it.
Also why would it be that a types generic type arguments would be used for the converter's generic type arguments? What about if a dev has a converter and they what the type they are converting as the type argument?
This feels like a solution that is in search of a problem.
Thank you for your time considering this feature request. This feature is definitely more of a power user feature and I can understand your hesitation adding it.
I'm currently using this feature with a custom contract resolver in an OSS project for a Value wrapper struct which also contains a dirty checking property. Before I realized this could be accomplished with a custom contract resolver, binding the converter to the type was a real pain as the converter had to be specified as non-generic with a type dictionary mapping to a generic converter through an interface as specified here.
I think the type's generic type arguments flowing over to the converter's generic type arguments makes sense when applied to types but I can see how others might expect something different when used on fields and properties. I originally only envisioned this to be applied to types.
As this can be handled with a custom contract resolver the lack of this feature is not a big problem to me. Again thank you for your consideration.
This is where I got up to when I stopped - https://github.com/JamesNK/Newtonsoft.Json/commit/7665113d955aee99f7a506103b142650d6c833d5
Hmmm, I'm torn on this feature. I'll give it more thought.
That looks really good.
I was thinking about your suggestion of using the actual type as the generic type argument for the converter. It seems to me this would only make sense if the type itself is a generic type parameter of the enclosing type such as in the following cases. I've updated my branch to support these cases at https://github.com/TylerBrinkley/Newtonsoft.Json/commit/b747a5c7a0dffcbab591996497816f3f410aa5ac
public sealed class ClassWithGenericEnumProperty<TEnum>
{
[JsonConverter(typeof(GenericEnumConverter<>))]
public TEnum EnumValue { get; set; }
}
public sealed class ClassWithGenericEnumCollectionProperty<TEnum>
{
[JsonProperty(ItemConverterType = typeof(GenericEnumConverter<>))]
public TEnum[] EnumValues { get; set; }
}
[JsonArray(ItemConverterType = typeof(GenericEnumConverter<>))]
public sealed class ListWithEnumItemGenericConverter<TEnum> : List<TEnum>
{
}
[JsonDictionary(ItemConverterType = typeof(GenericEnumConverter<>))]
public sealed class DictionaryWithEnumItemGenericConverter<TKey, TEnum> : Dictionary<TKey, TEnum>
{
}
public sealed class GenericEnumConverter<TEnum> : JsonConverter
{
public override bool CanConvert(Type objectType) => objectType == typeof(TEnum);
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Integer)
{
return Enum.ToObject(typeof(TEnum), reader.Value);
}
return Enum.Parse(typeof(TEnum), reader.Value.ToString().Substring(typeof(TEnum).Name.Length + 1));
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
string str = value.ToString();
if (char.IsDigit(str[0]) || str[0] == '-' || str[0] == '+')
{
writer.WriteRawValue(str);
}
else
{
writer.WriteValue($"{typeof(TEnum).Name}.{str}");
}
}
}
Morning all, indeed it seems that not many people find it useful. I would have found it useful today. my use case: the serializer is not within my control, hence I use the JsonConvert attribute. I am writing a graph query where I get some kind of tree returned. The tree structure itself is somewhat peculiar but always has the same structure - meanwhile the payload can change. So ideally I would want to serialize to a
[JsonConverter(typeof(GremlinTreeConverter<T>)]
class GremlinTree<T>
However, what I do now is to have a non-generic converter that build the generic one with reflection based on the objectType
that enters the converter. If it would be used a lot I would have a cache of pre-built converters that dispatches based on the objectType
@flq that was my initial way of handling the lack of support for this but then switched to the custom contract resolver route as I had direct control over the serialization of the type.
It'd be great if the PR #1359 for this would be accepted.
I would also find use in this feature. Thanks for providing the custom contract resolver work around.
I just found a case where I needed this too. Basically, this can be useful any time you want to customize the serialization of a generic type.
I came upon this same issue and @TylerBrinkley 's CustomContractResolver saved the day. Thanks a lot! I don't think custom serialization of a generic type is that exotic, so would be great to have native support in the future.
Hello, JamesNK Can you merge this pull-request into master? This problem is very urgent for me Thank you very much Artem
With the current version of Json.NET I was able to implement this same thing using the custom contract resolver below. But I do think this should be implemented directly within Json.NET since it isn't a breaking change as the existing behavior throws an exception and the feature can be very useful.
public sealed class CustomContractResolver : DefaultContractResolver { protected override JsonConverter ResolveContractConverter(Type objectType) { var typeInfo = objectType.GetTypeInfo(); if (typeInfo.IsGenericType && !typeInfo.IsGenericTypeDefinition) { var jsonConverterAttribute = typeInfo.GetCustomAttribute<JsonConverterAttribute>(); if (jsonConverterAttribute != null && jsonConverterAttribute.ConverterType.GetTypeInfo().IsGenericTypeDefinition) { return (JsonConverter)Activator.CreateInstance(jsonConverterAttribute.ConverterType.MakeGenericType(typeInfo.GenericTypeArguments), jsonConverterAttribute.ConverterParameters); } } return base.ResolveContractConverter(objectType); } }
Hello, Tyler. Can you describe how can we use CustomContractResolver in order to pass generic parameters to custom attribute?
Here is how I'm using it. I created CustomContractResolver (yours) and I'm intending using it like this:
var response = await ExecuteAsync(resource, method, data);
var settings = new JsonSerializerSettings {ContractResolver = new CustomContractResolver()};
var result = JsonConvert.DeserializeObject
But I'm getting error while passing generic type as parameter to json-converter: [JsonConverter(typeof(SingleValueCollectionConverter
If I use generic attribute without passing parameter like so [JsonConverter(typeof(SingleValueCollectionConverter<>))] I currently get an ArgumentException of "Invalid type owner for DynamicMethod."
Where am I wrong? Thanks
If I use generic attribute without passing parameter like so [JsonConverter(typeof(SingleValueCollectionConverter<>))] I currently get an ArgumentException of "Invalid type owner for DynamicMethod."
The above should work just fine so long as you're using the CustomContractResolver
. I'm guessing you're serializing or deserializing the type somewhere else in your code which isn't using the CustomContractResolver
.
Sorry, but could you explain how to modify the code above (for ResolveContractConverter) if I need more than 1 converter (for example, in the case I need to use different converters for different properties in my class)?
public class ExpresssionDTO<TTable> : BaseDTO where TTable : class
{
[JsonProperty(ItemConverterType = typeof(PredicateSerializationConverter<>))]
public ICollection<Expression<Func<TTable, bool>>> Predicates { get; set; } = new List<Expression<Func<TTable, bool>>>();
[JsonConverter(converterType: typeof(FilterSerializationConverter<>))]
public Expression<Func<TTable, object>> Filter { get; set; } = null;
}
where
public class PredicateSerializationConverter<TTable> : ExpressionSerializer<TTable, bool> where TTable : class
{
public PredicateSerializationConverter() :base()
{
}
}
public class FilterSerializationConverter<TTable> : ExpressionSerializer<TTable, object> where TTable : class
{
public FilterSerializationConverter() : base()
{
}
}
public class ExpressionSerializer<T, U> : JsonConverter where T : class
{
...
}
I get an error
Cannot create an instance of WebFoundationClassesCore.ServiceClasses.Converters.PredicateSerializationConverter`1[TTable] because Type.ContainsGenericParameters is true.
in the code:
public class LongNameContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
**var list = base.CreateProperties(type, memberSerialization);**
foreach (JsonProperty prop in list)
{
prop.PropertyName = prop.UnderlyingName;
}
return list;
}
}
Consider the following value wrapper struct and its associated json converter.
Running the following code I currently get an
ArgumentException
of "Invalid type owner for DynamicMethod."JsonConvert.SerializeObject(new Value<bool>(true));
Obviously the problem is that the converter type provided with the
JsonConverterAttribute
is an open generic type and Json.NET doesn't know how to instantiate it. The provided converter type is open because currentlyC#
doesn't allow use of a types' type parameters in its attributes.When an open generic converter type is provided I would like Json.NET to implicitly use the generic type arguments of the type the attribute is applied to using the
MakeGenericType
method onType
in order to instantiate the json converter.