0

I have the below Model class,

using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;

public class FormField
{
    [Required]
    [JsonPropertyName("STD_USERTYPEID")]
    public string UserTypeId { get; set; }

    [Required]
    [JsonPropertyName("STD_OFFICETYPEID")]
    public string OfficeTypeId { get; set; }
}

I have a few scenarios where STD_OFFICETYPEID may come as LegacyOFFICETYPEID or OfficeID. Is there a way in which I can dynamically generate JsonPropertyName? I am using System.Text.Json NuGet package.

Note that this example is simplified. In my production code there could be 20-25 concrete properties. And all of these properties could map to 5-10 different JsonPropertyNames each.

dbc
  • 104,963
  • 20
  • 228
  • 340
tRuEsAtM
  • 3,517
  • 6
  • 43
  • 83
  • 2
    In short - you can't. You can workaround with custom converters/properties or using dictionaries instead of objects. – Guru Stron Sep 05 '22 at 18:56
  • Does this answer your question? [dynamically change the json property name and serialize](https://stackoverflow.com/questions/44433732/dynamically-change-the-json-property-name-and-serialize) – Mohammad Aghazadeh Sep 05 '22 at 19:14
  • 1
    @MohammadAghazadeh - question is for System.Text.Json not Json.NET. – dbc Sep 05 '22 at 19:18
  • You could add a "semi-private" surrogate property `[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull), JsonInclude] public string LegacyOFFICETYPEID { private get { return null; } set { OfficeTypeId = value; } }`. Either the setter or getter must be public because System.Text.Json refuses to serialize an entirely private property even when marked with `[JsonInclude]` -- it's less flexible than even `DataContractJsonSerializer` in that regard. – dbc Sep 05 '22 at 19:22
  • @dbc Oh my bad, in general I meant that it should create its own custom json converter – Mohammad Aghazadeh Sep 05 '22 at 19:23
  • Demo of using surrogate properties here: https://dotnetfiddle.net/2eDN8S – dbc Sep 05 '22 at 19:32
  • @dbc Appreciate the use of Surrogate properties, but dynamic names are not just limited to 2 properties, there could be 20-25 dynamic properties. Do you know how can I achieve it? – tRuEsAtM Sep 05 '22 at 19:35
  • There's no particularly convenient way to do this with System.Text.Json. Do all 20-25 JSON properties map to the same property or do they map to multiple current properties? – dbc Sep 05 '22 at 19:42
  • So, in all there could be 20-25 concrete properties. And all of these properties could map to 5-10 different JsonPropertyNames. – tRuEsAtM Sep 05 '22 at 19:44
  • Ugh. Well you could do something with `JsonExtensionData` and `IJsonOnDeserialized.OnDeserialized()` I suppose, see e.g. https://dotnetfiddle.net/Y6t0lt. Other than that it looks like you'll need some fairly complex `JsonConverter`. – dbc Sep 05 '22 at 19:54
  • Does the `JsonExtensionData` approach answer your question? – dbc Sep 05 '22 at 20:41
  • @tRuEsAtM It would be much easier to use Newtonsoft.Json or a Dictionary – Serge Sep 05 '22 at 23:10
  • @Serge Any sample? – tRuEsAtM Sep 05 '22 at 23:47
  • @tRuEsAtM I can create some code, if newtonsoft.json could be an option for you – Serge Sep 05 '22 at 23:53
  • @Serge Yeah, I can switch to it. – tRuEsAtM Sep 06 '22 at 00:18
  • @tRuEsAtM - if you are willing to switch to Json.NET you can create a [custom contract resolver](https://www.newtonsoft.com/json/help/html/contractresolver.htm#CustomIContractResolverExamples) that adds in the legacy properties automatically. See e.g. [json deserialize from legacy property names](https://stackoverflow.com/a/33156862/3744182). – dbc Sep 06 '22 at 00:21
  • @tRuEsAtM Ok, I created my code – Serge Sep 06 '22 at 22:47

2 Answers2

0

I'll tell you how I would do this: source generators.

First I would inject a new attribute, JsonPropertyNames(params string[] alternativeNames), and I'd decorate my class with it instead, giving it the full list of possible field names.

Then I'd have the source generator generate a second class matching properties with my original class, including properties for each of the alternative names provided using JsonPropertyNames. This is the class you'd be reading the Json into, and all your properties would get read in one of the properties.

Then the generator would add all the necessary AutoMapper mapping code to copy from my generated type to the original type, as well as a helper class that reads into the generated type and invokes AutoMapper to return the class for you.

So from the caller side, you'd just need to call one function to get your type, ignoring all the details behind the scenes.

Blindy
  • 65,249
  • 10
  • 91
  • 131
0

if you are ready to swith to Newtonsoft.Json you can try this code

var json="{\"STD_USERTYPEID\":\"userId\",\"LegacyOFFICETYPEID\":\"officeId\"}";

FormField formField = DeserializeObj<FormField>(json);

public class FormField
{
    [JsonProperty("STD_USERTYPEID")]
    public string UserTypeId { get; set; }
    
    [JsonPropertyNames(new string[] {"STD_OFFICETYPEID", "LegacyOFFICETYPEID" })]
    public string OfficeTypeId { get; set; }
}

public T DeserializeObj<T>(string json) where T:new()
{
    var jsonObj = JObject.FromObject(new T());
    var attrs = GetAttrs<T>();
    var jsonParsed = JObject.Parse(json);
    foreach (var prop in jsonParsed.Properties())
    {
        var propName=prop.Name;
        
        var attr=attrs.Where(a=>a.AttributeNames.Contains(propName)).FirstOrDefault();
        if(attr!=null) jsonObj[attr.PropertyName]=prop.Value;
        else jsonObj[propName]=prop.Value;
    }
    return jsonObj.ToObject<T>();
}
public static List<PropertyAttributes> GetAttrs<T>()  where T: new()
{
    var source= new T();
    var attrs = new List<PropertyAttributes>();
    
    foreach (PropertyInfo prop in source.GetType().GetProperties())
    {
        var attribute = prop.GetCustomAttribute<JsonPropertyNamesAttribute>();

        if (attribute != null)
        {
            attrs.Add(new PropertyAttributes { PropertyName = prop.Name, AttributeNames = attribute.Names });
        }
    }
    if (attrs.Count > 0) return attrs;
    return null;
}

public class PropertyAttributes
{
    public string PropertyName { get; set; }
    public string[] AttributeNames { get; set; }
}

[AttributeUsage(AttributeTargets.All)]
public class JsonPropertyNamesAttribute : Attribute
{
    private string[] names;

    public JsonPropertyNamesAttribute(string[] names)
    {
        this.names = names;
    }
    public virtual string[] Names
    {
        get { return names; }
    }
}
Serge
  • 40,935
  • 4
  • 18
  • 45