In C#, how can I set, reset, or define a JsonProperty attribute at runtime?

796 Views Asked by At

We have a microservice developed in C# which needs to integrate with Salesforce.

We are implementing a new instance of Saleforce and for some stupid reason, someone decided to rename one field from My_Field__c to MyField__c and they don't want to have any custom fields with any avoidable underscores. Stupid, but not my choice.

Now I need to integrate a new instance of the microservice to the new instance of Salesforce making the solution different by only a single character, but of course we need to be able to maintain both the original microservice and the new microservice.

In an ideal world, I would just set some value in appSettings and consume this in my declaration of [JsonProperty(PropertyName = "My_Field__c")], but of course the attribute requires a compile time constant, so we can't do something so simple.

Creating a custom serializer/deserializer or maintaing a different git branch to remove this one character feels like overkill.

Is there some way I can just set this one attribute dynamically?

(TypeDescriptor was suggested as possibly offering a solution, but I can't find details on how I might apply this.)

2

There are 2 best solutions below

0
Brian Kessler On BEST ANSWER

I tried to get TypeDescriptor.AddAttributes() to work with the Type, but this did not work. As the instance would not exist before deserialization, attempting to use it with an instance was a non-starter.

However, I figured out how to make this work with a Contract Resolver:


public static MyModel FromSerializedMyModel(string content, SalesforceFieldNames salesforceFieldNames) =>
   JsonConvert.DeserializeObject<MyModel>(contents, 
       CreateSettings(salesforceFieldNames));

private static JsonSerializerSettings CreateSettings(SalesforceFieldNames salesforceFieldNames) => new()
        {
            ContractResolver = new MyModelContractResolver(salesforceFieldNames)
        };



class MyModelContractResolver: DefaultContractResolver
    {
        private readonly SalesforceFieldNames _salesforceFieldNames;

        public MyModelContractResolver(SalesforceFieldNames salesforceFieldNames) =>
            _salesforceFieldNames = salesforceFieldNames;

        protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
        {
            IList<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
            Dictionary<string, JsonProperty> propertiesByUnderlyingName = properties.ToDictionary(x => x.UnderlyingName, x => x);
            if (propertiesByUnderlyingName.TryGetValue("MyField", out JsonProperty spvTagProperty))
            {
                spvTagProperty.PropertyName = _salesforceFieldNames.MyField;
            }
            return properties;
        }
    }

A bit verbose and otherwise ugly, but it works...

3
Max On

Add two separate properties for the old and new property name and a helper property which returns the one that's been deserialized:

public class MyModel
{
   [JsonProperty(PropertyName = "My_Field__c")]
   public string MyFieldOld { get; set; }

   [JsonProperty(PropertyName = "MyField__c")]
   public string MyFieldNew { get; set; }

   [JsonIgnore]
   public string MyField => MyFieldOld ?? MyFieldNew;

}