Search code examples
c#.netjson.netasp.net-core-webapi

ASP.NET Web API : data object with JsonPropertyName attribute expect the payload to be the JsonPropertyName and i need the reular propertyName instead


This is the API method:

[Authorize]
[HttpPost("pa/questionnaire")]
[ProducesResponseType(200, Type = typeof(ApiResponse))]
[ProducesResponseType(500, Type = typeof(ApiResponse))]
public async Task<IActionResult> FillPA([FromBody] QuestionnaireData data)

This is the QuestionnaireData class:

public class QuestionnaireData
{
    public string Id { get; set; }
    public DateTime? SignedDate { get; set; }
    public Questionnaire Questionnaire { get; set; }
}

public class Questionnaire
{   
    [JsonPropertyName("patient_signature_date")]
    public DateTime? PatientSignatureDate { get; set; }

    [JsonPropertyName("patient_dob")]
    public DateTime? PatientDob { get; set; }
    [JsonPropertyName("patient_email")]
    public string PatientEmail { get; set; }
    
    [JsonPropertyName("patient_gender")] 
    public string PatientGender { get; set; }

    [JsonPropertyName("patient_cell_phone")]
    public string PatientCellPhone { get; set; }

    [JsonPropertyName("patient_address")]
    public string PatientAddress { get; set; }

    [JsonPropertyName("patient_city")]
    public string PatientCity { get; set; }
}

I need to use this payload (with original properties name):

{
    "Id": "234567",
    "signedDate": "2024-09-30T00:00:00",
    "questionnaire": {
                         "patientEmail": "september18@yopmail.com",
                         "patientCellPhone": "(817)-123-4567",
                         "patientDob": "2001-02-02",
                         "patientGender": "female",
                         "patientAddress": "96 FOREST ST",
                         "patientCity":"BOSTON"
                     }
}

But instead it only works with the json property name :

{
    "Id": "a5UU90000002VEEMA2",
    "signedDate": "2024-09-30T00:00:00",
    "Questionnaire": {
         "patient_dob": "2024-09-30T08:16:06.509Z",
         "patient_email": "string",
         "patient_gender": "string",
         "patient_cell_phone": "string",
         "patient_address": "string",
         "patient_city": "string"
    }
}

I need the attribute for future use (in order - to convert the object values to key value pairs and post it to another API), but not for the payload naming,

Does anyone have any idea?


Solution

  • I create a sample and make it works, here is my detailed steps.

    The structure of project

    enter image description here

    Test Code

    1. CaseInsensitiveConverter.cs

    using System.Reflection;
    using System.Text.Json;
    using System.Text.Json.Serialization;
    using System.Text.RegularExpressions;
    
    namespace WebApplication1
    {
        public class CaseInsensitiveConverter<T> : JsonConverter<T> where T : new()
        {
            public override T Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
            {
                if (reader.TokenType != JsonTokenType.StartObject)
                    throw new JsonException();
    
                var instance = new T();
                var properties = typeToConvert.GetProperties(BindingFlags.Instance | BindingFlags.Public);
    
                while (reader.Read())
                {
                    if (reader.TokenType == JsonTokenType.EndObject)
                        return instance;
    
                    if (reader.TokenType != JsonTokenType.PropertyName)
                        throw new JsonException();
    
                    var propertyName = reader.GetString();
                    var property = properties.FirstOrDefault(p =>
                        string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase) ||
                        string.Equals(ToSnakeCase(p.Name), propertyName, StringComparison.OrdinalIgnoreCase));
    
                    if (property == null)
                    {
                        reader.Skip();
                        continue;
                    }
    
                    reader.Read();
    
                    var value = JsonSerializer.Deserialize(ref reader, property.PropertyType, options);
                    property.SetValue(instance, value);
                }
    
                return instance;
            }
    
            public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
            {
                JsonSerializer.Serialize(writer, value, options);
            }
    
            private string ToSnakeCase(string input)
            {
                return Regex.Replace(input, "([a-z0-9])([A-Z])", "$1_$2").ToLower();
            }
        }
    }
    

    2. Use it in model.

    using Microsoft.AspNetCore.Mvc;
    using System.Text.Json.Serialization;
    
    namespace WebApplication1.Models
    {
        public class QuestionnaireData
        {
            public string Id { get; set; }
            public DateTime? SignedDate { get; set; }
            [JsonConverter(typeof(CaseInsensitiveConverter<Questionnaire>))]
            public Questionnaire Questionnaire { get; set; }
        }
    
        public class Questionnaire
        {
            [JsonPropertyName("patient_signature_date")]
            public DateTime? PatientSignatureDate { get; set; }
    
            [JsonPropertyName("patient_dob")]
            public DateTime? PatientDob { get; set; }
            [JsonPropertyName("patient_email")]
            public string PatientEmail { get; set; }
    
            [JsonPropertyName("patient_gender")]
            public string PatientGender { get; set; }
    
            [JsonPropertyName("patient_cell_phone")]
            public string PatientCellPhone { get; set; }
    
            [JsonPropertyName("patient_address")]
            public string PatientAddress { get; set; }
    
            [JsonPropertyName("patient_city")]
            public string PatientCity { get; set; }
        }
    }
    

    3. Register it (Use System.Text.Json, not Newtonsoft)

    builder.Services.AddControllers().AddJsonOptions(options =>
    {
        options.JsonSerializerOptions.Converters.Add(new CaseInsensitiveConverter<Questionnaire>());
        options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
    });
    

    4. Test Result

    Use default one.

    enter image description here

    USE BELOW PAYLOAD.

    {
        "Id": "234567",
        "signedDate": "2024-09-30T00:00:00",
        "questionnaire": {
                             "patientEmail": "september18@yopmail.com",
                             "patientCellPhone": "(817)-123-4567",
                             "patientDob": "2001-02-02",
                             "patientGender": "female",
                             "patientAddress": "96 FOREST ST",
                             "patientCity":"BOSTON"
                         }
    }
    

    enter image description here