1

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.

dbc
  • 104,963
  • 20
  • 228
  • 340
MetaGuru
  • 42,847
  • 67
  • 188
  • 294
  • `[JsonConverter(typeof(ComponentsConverter), "custom_string_value", someCustomObject)]` won't compile because attribute arguments [must be a constant expression](https://stackoverflow.com/q/25859094/3744182). So is your question *How can I pass a non-constant value in [`JsonConverterAttribute.ConverterParameters`](https://www.newtonsoft.com/json/help/html/P_Newtonsoft_Json_JsonConverterAttribute_ConverterParameters.htm)?* Or is your question, *How to access `JsonConverterAttribute.ConverterParameters` from within the converter?* – dbc Jun 08 '21 at 15:27
  • @dbc I am OK with them being constants, those were just placeholder examples (updated it to be more clear) for showing where I wanted to pass things, and yes I am not sure how to access them from within the converter – MetaGuru Jun 08 '21 at 15:40
  • Well Json.NET just calls `Activator.CreateInstance(ConverterType, ConverterParameters)` so the converter parameters are passed into the converter's constructor. You can remember them there and use them in ReadJson() and WriteJson(). See e.g. [Customising Json.NET serialisation based on compile time type](https://stackoverflow.com/a/67862096/3744182) and https://dotnetfiddle.net/CjRRtn – dbc Jun 08 '21 at 15:44
  • @dbc awesome if you post this as the answer I'll award it to you, ideally paste your code into the answer as well – MetaGuru Jun 08 '21 at 15:56

1 Answers1

0

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).

dbc
  • 104,963
  • 20
  • 228
  • 340