Search code examples
.net-corejson.netmodel-bindingcustom-attributesjsonconvert

Is there a way to access customParameters object[] from within a custom JsonConverter class implementation?


I wrote a custom JsonConverter called CompententsConverter and it works fine, however I'm curious if there is a way to make use of the alternate constructor which takes params object[] converterParameters and pass over my own custom parameters from the attribute accordingly.

Likewise, I am not sure how to actually retrieve the parameters inside the JsonConverter class definition or if it's even possible to do this with a JsonConverter attribute.

inside the model, attribute with theoretical params, where some_parameter_here is a placeholder for a constant expression:

[JsonProperty("components")]
[JsonConverter(typeof(ComponentsConverter), some_parameter_here)]
public List<ComponentModel> Components { get; set; }

ComponentsConverter custom JsonConverter:

public class ComponentsConverter : JsonConverter
{   
    public override bool CanConvert (Type t) => t == typeof(List<ComponentModel>);

    public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        // ... any way to access params object[] customParameters here?
    }
    public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer)
    {
        // ...
    }
}

Would be nice to be able to define some custom conversion behavior for specific model properties by using these extra params.


Solution

  • Json.NET basically just calls Activator.CreateInstance(ConverterType, ConverterParameters) [1] so the converter parameters are passed into the converter's constructor. You can remember them there and use them in ReadJson() and WriteJson() e.g. like so:

    public class ComponentsConverter : JsonConverter
    {   
        public string CustomString { get; init; }
    
        public ComponentsConverter(string customString)
        {
            // Remember the converter parameters for use in WriteJson() and ReadJson()
            this.CustomString = customString;
        }
    
        public override bool CanConvert (Type t) => t == typeof(List<ComponentModel>);
    
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Customize the serialized contents using the CustomString passed in to the constructor
            writer.WriteValue(CustomString);
        }
        
        public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // ...
        }
    }
    

    For a converter applied to the items of a collection, use JsonPropertyAttribute.ItemConverterType with JsonPropertyAttribute.ItemConverterParameters. E.g.:

    [JsonProperty("components",
                  ItemConverterType = typeof(ComponentConverter), 
                  ItemConverterParameters = new object [] { "custom_string_value" })]
    

    And then:

    public class ComponentConverter : JsonConverter
    {   
        public string CustomString { get; init; }
    
        public ComponentConverter(string customString)
        {
            // Remember the converter parameters for use in WriteJson() and ReadJson()
            this.CustomString = customString;
        }
    
        public override bool CanConvert (Type t) => t == typeof(ComponentModel);
    
        public override void WriteJson (JsonWriter writer, object value, JsonSerializer serializer)
        {
            // Customize the serialized contents using the CustomString passed in to the constructor
            writer.WriteValue(CustomString);
        }
        
        public override object ReadJson (JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            // ...
        }
    }
    

    Demo fiddles here and here.


    Footnotes:

    [1]: I'm oversimplifying a little here. It actually uses code-generation techniques to generate and cache delegates on the fly that do the same thing as Activator.CreateInstance() but without the performance penalties of late-bound reflection. See e.g. ExpressionReflectionDelegateFactory.ObjectConstructor<object> CreateParameterizedConstructor(MethodBase method).