.NET Core 2.2 WebAPI序列化与反序列化适配问题求助
Hey there! Let's work through your two serialization/deserialization problems step by step—no extra model conversions required, and we'll make that generic PostAsync method flexible enough to handle both snake_case and camelCase APIs.
Problem 1: Model returns snake_case to mobile when you want camelCase
Your LoginResponseModel uses [JsonProperty(PropertyName = "token_type")] which works great for deserializing third-party snake_case APIs, but this attribute is bidirectional—it tells Newtonsoft.Json to use the snake_case name both when reading and writing JSON. That's why your mobile response ends up in snake_case.
Fix 1: Use [JsonAlias] (Recommended)
If you can update your Newtonsoft.Json package to version 12.0.1 or higher, replace [JsonProperty] with [JsonAlias]. This attribute only affects deserialization (it tells the serializer to accept the snake_case alias), while serialization will use your property's camelCase name.
Updated LoginResponseModel:
public class LoginResponseModel { [JsonAlias("token_type")] public string tokenType { get; set; } [JsonAlias("access_token")] public string accessToken { get; set; } [JsonAlias("expires_in")] public string expiresIn { get; set; } [JsonAlias("refresh_token")] public string refreshtToken { get; set; } }
Then, ensure your WebAPI is configured to output camelCase globally. Add this to Startup.cs in the ConfigureServices method:
services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); });
Now when you return LoginResponseModel to mobile, it'll be in camelCase, and you can still deserialize third-party snake_case responses without issues.
Fix 2: Custom Contract Resolver (If you can't update Newtonsoft.Json)
If you're stuck on an older Newtonsoft version, create a custom contract resolver that ignores [JsonProperty] during serialization but respects it during deserialization:
public class SerializeCamelCaseDeserializeJsonPropertyResolver : CamelCasePropertyNamesContractResolver { protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); // For serialization: use the property's camelCase name var originalPropertyName = member.Name.ToCamelCase(); property.PropertyName = originalPropertyName; // For deserialization: add the [JsonProperty] value as an alias var jsonPropAttr = member.GetCustomAttribute<JsonPropertyAttribute>(); if (jsonPropAttr != null) { property.AlternateNames = property.AlternateNames.Concat(new[] { jsonPropAttr.PropertyName }).ToArray(); } return property; } } // Helper extension method for camelCase conversion public static string ToCamelCase(this string input) { if (string.IsNullOrEmpty(input)) return input; return char.ToLowerInvariant(input[0]) + input.Substring(1); }
Then update your global WebAPI settings to use this resolver instead of the default:
services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new SerializeCamelCaseDeserializeJsonPropertyResolver(); });
Problem 2: Generic PostAsync can't handle camelCase APIs
Your current PostAsync hardcodes snake_case deserialization, which breaks when the third-party API returns camelCase. We'll make this method flexible to accept custom deserialization settings.
Fix: Add an Optional Parameter for Deserialization Settings
Modify your PostAsync method to accept an optional JsonSerializerSettings parameter. If no settings are provided, it defaults to snake_case (keeping your existing behavior for snake_case APIs):
private async Task<object> PostAsync<T1,T2>(string uri, T2 content, JsonSerializerSettings deserializationSettings = null) { using (var requestMessage = new HttpRequestMessage(HttpMethod.Post, uri)) { var json = JsonConvert.SerializeObject(content); using (var stringContent = new StringContent(json, Encoding.UTF8, "application/json")) { requestMessage.Content = stringContent; HttpResponseMessage response = await _client.SendAsync(requestMessage); if (response.IsSuccessStatusCode) { _logger.LogInformation("Request Succeeded"); // Use provided settings, or default to snake_case var settings = deserializationSettings ?? new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }; T1 responseModel = JsonConvert.DeserializeObject<T1>(await response.Content.ReadAsStringAsync(), settings); return responseModel; } else { return await GetFailureResponseModel(response); } } } }
Usage Examples
- For snake_case APIs (no change needed):
var loginResponse = await PostAsync<LoginResponseModel, LoginRequestModel>("https://third-party-snake-api.com/login", loginRequest); - For camelCase APIs:
var camelCaseSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var camelCaseResponse = await PostAsync<CamelCaseResponseModel, RequestModel>("https://third-party-camel-api.com/data", request, camelCaseSettings);
Bonus: Add Convenience Methods (Optional)
To make calling easier, add wrapper methods for common naming strategies:
public async Task<object> PostSnakeCaseAsync<T1,T2>(string uri, T2 content) { return await PostAsync<T1,T2>(uri, content, new JsonSerializerSettings { ContractResolver = new DefaultContractResolver { NamingStrategy = new SnakeCaseNamingStrategy() } }); } public async Task<object> PostCamelCaseAsync<T1,T2>(string uri, T2 content) { return await PostAsync<T1,T2>(uri, content, new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }); }
Now you can call:
var loginResponse = await PostSnakeCaseAsync<LoginResponseModel, LoginRequestModel>("https://third-party-snake-api.com/login", loginRequest); var camelCaseResponse = await PostCamelCaseAsync<CamelCaseResponseModel, RequestModel>("https://third-party-camel-api.com/data", request);
内容的提问来源于stack exchange,提问作者Shabir jan

