Search code examples
c#jsonserializationintegrationtypedescriptor

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


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


Solution

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