如何基于Newtonsoft实现JSON属性名双向映射(避免重复序列化反序列化)
Nice problem to solve! Let's walk through how to handle this bidirectional property name conversion without redundant serialization/deserialization steps—since you've already got JsonProperty attributes set up, we can leverage those mappings directly with Newtonsoft.Json's JObject to keep things efficient.
Core Idea
Instead of serializing/deserializing your entity classes back and forth (which adds unnecessary overhead), we'll directly manipulate the in-memory JSON structure using JObject. First, we'll cache the property name mappings (long ↔ short) from your JsonProperty attributes, then use those mappings to rename properties in the JObject recursively (to handle nested objects and arrays).
Step 1: Cache Property Mappings
Create a static cache to store name mappings for each data type. This avoids repeated reflection calls, which is critical given the number of classes you're working with.
public static class PropertyMappingCache { private static readonly Dictionary<Type, (Dictionary<string, string> LongToShort, Dictionary<string, string> ShortToLong)> _typeMappings = new(); public static (Dictionary<string, string> LongToShort, Dictionary<string, string> ShortToLong) GetMappings(Type entityType) { if (_typeMappings.TryGetValue(entityType, out var mappings)) return mappings; var longToShort = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var shortToLong = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); foreach (var prop in entityType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { var jsonAttr = prop.GetCustomAttribute<JsonPropertyAttribute>(); var shortName = jsonAttr?.PropertyName ?? prop.Name; longToShort[prop.Name] = shortName; shortToLong[shortName] = prop.Name; } var result = (longToShort, shortToLong); _typeMappings[entityType] = result; return result; } }
Step 2: Build the Conversion Utility
Next, create a utility class that uses the cached mappings to rename properties in JObject instances. This handles nested objects and arrays recursively, so you don't have to worry about complex data structures.
public static class JsonPropertyRenamer { // Convert long frontend names to short storage names public static JObject ToShortNames(JObject sourceJObject, Type entityType) { var (longToShort, _) = PropertyMappingCache.GetMappings(entityType); return RenameProperties(sourceJObject, longToShort, entityType); } // Convert short storage names to long frontend names public static JObject ToLongNames(JObject sourceJObject, Type entityType) { var (_, shortToLong) = PropertyMappingCache.GetMappings(entityType); return RenameProperties(sourceJObject, shortToLong, entityType); } private static JObject RenameProperties(JObject source, Dictionary<string, string> nameMap, Type parentType) { var target = new JObject(); foreach (var prop in source.Properties()) { // Get the new name (fallback to original if no mapping exists) var newName = nameMap.TryGetValue(prop.Name, out var mappedName) ? mappedName : prop.Name; // Handle nested objects/arrays JToken newValue; if (prop.Value.Type == JTokenType.Object) { // Find the nested property type from the parent entity class var nestedProp = parentType.GetProperty(prop.Name); newValue = nestedProp != null ? RenameProperties((JObject)prop.Value, nameMap, nestedProp.PropertyType) : prop.Value; } else if (prop.Value.Type == JTokenType.Array) { newValue = ProcessArray((JArray)prop.Value, parentType, prop.Name, nameMap); } else { newValue = prop.Value; } target.Add(newName, newValue); } return target; } private static JArray ProcessArray(JArray sourceArray, Type parentType, string propertyName, Dictionary<string, string> nameMap) { var targetArray = new JArray(); var elementType = GetArrayElementType(parentType, propertyName); foreach (var item in sourceArray) { if (item.Type == JTokenType.Object && elementType != null) { targetArray.Add(RenameProperties((JObject)item, nameMap, elementType)); } else { targetArray.Add(item); } } return targetArray; } private static Type GetArrayElementType(Type parentType, string propertyName) { var prop = parentType.GetProperty(propertyName); if (prop == null) return null; var propType = prop.PropertyType; // Handle List<T> if (propType.IsGenericType && propType.GetGenericTypeDefinition() == typeof(List<>)) { return propType.GetGenericArguments()[0]; } // Handle arrays (T[]) else if (propType.IsArray) { return propType.GetElementType(); } return null; } }
Step 3: Usage Examples
Frontend to Backend (Long → Short Names)
When receiving data from the frontend, parse the JSON into a JObject, convert to short names, then store it in PostgreSQL's JSONB field:
// Example: Frontend sends JSON with long property names var frontendJson = @"{""UserFullName"": ""John Doe"", ""UserContactDetails"": {""PhoneNumber"": ""555-1234""}}"; var frontendJObject = JObject.Parse(frontendJson); // Convert to short names (using your data class type) var shortNameJObject = JsonPropertyRenamer.ToShortNames(frontendJObject, typeof(User)); // Store in PostgreSQL (assuming your entity has a Jsonb field named `JsonData`) var userEntity = new UserEntity { JsonData = shortNameJObject }; dbContext.UserEntities.Add(userEntity); await dbContext.SaveChangesAsync();
Backend to Frontend (Short → Long Names)
When fetching data from the database, take the short-name JObject, convert to long names, then send it to the frontend:
// Fetch entity from DB var userEntity = await dbContext.UserEntities.FirstOrDefaultAsync(u => u.Id == userId); if (userEntity == null) return NotFound(); // Convert short names back to long frontend names var longNameJObject = JsonPropertyRenamer.ToLongNames(userEntity.JsonData, typeof(User)); // Return to frontend (ASP.NET Core example) return Ok(longNameJObject);
Key Benefits
- No redundant serialization: We work directly with
JObject, so we avoid converting JSON ↔ entity class ↔ JSON (saving CPU and memory). - Cached mappings: Reflection only happens once per data type, so performance stays consistent even with many classes.
- Handles nested structures: Recursive logic ensures nested objects and arrays are properly renamed.
- Backward compatible: Properties without
JsonPropertyattributes keep their original names, so you don't break existing code.
内容的提问来源于stack exchange,提问作者neoKurt

