如何实现多语言JSON属性键名序列化?转换器与契约解析器选型
Great question! Let's break down how to solve your multi-language JSON serialization problem step by step.
1. Implementing MultiLangConverter.WriteJson
First up: building out the MultiLangConverter class. The core idea is to inspect each field (or property) on your object, check if it has a translated name for your target language, and use that instead of the original field name when writing the JSON. If no translated name exists, we fall back to the original name.
Here's a complete, working implementation:
public class MultiLangConverter : JsonConverter { private readonly Language _targetLang; public MultiLangConverter(Language targetLang) { _targetLang = targetLang; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value == null) { writer.WriteNull(); return; } writer.WriteStartObject(); var objectType = value.GetType(); // Iterate over all public instance fields (swap to GetProperties if using properties instead) foreach (var field in objectType.GetFields(BindingFlags.Public | BindingFlags.Instance)) { // Find the translated name attribute matching our target language var translatedAttr = field.GetCustomAttributes<TranslatedFieldName>() .FirstOrDefault(attr => attr.Lang == _targetLang); // Use translated name if available, else original field name var jsonKey = translatedAttr?.Name ?? field.Name; var fieldValue = field.GetValue(value); // Write the key-value pair to the JSON output writer.WritePropertyName(jsonKey); serializer.Serialize(writer, fieldValue); } writer.WriteEndObject(); } // Note: Add deserialization logic here if you need to parse multi-language JSON back to objects public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { throw new NotImplementedException("Deserialization not implemented for MultiLangConverter"); } public override bool CanConvert(Type objectType) { // Only apply this converter to types with fields marked with TranslatedFieldName return objectType.GetFields(BindingFlags.Public | BindingFlags.Instance) .Any(field => field.GetCustomAttributes<TranslatedFieldName>().Any()); } }
Key Details:
- We use reflection to scan for public fields (swap
GetFieldswithGetPropertiesif your class uses properties instead of public fields). - The converter checks for
TranslatedFieldNameattributes that match the language you specify in the constructor. - If no matching translated name is found, it defaults to the original field name—so your standard English serialization works without needing the converter.
2. JsonConverter vs. ContractResolver: Which is Better?
Both approaches work, but they serve different use cases. Let's break down the pros and cons:
When to use JsonConverter:
- Per-object/type control: If only specific classes need multi-language serialization, applying the converter directly (like in your example) is simple and straightforward.
- Self-contained logic: All language-specific serialization logic lives in one class, making it easy to tweak or replace if needed.
When to use ContractResolver:
- Global application-wide behavior: If every class with
TranslatedFieldNameattributes should use multi-language serialization by default (for a given language), a resolver is more efficient. It modifies the serialization "contract" once, rather than applying a converter to each object. - Better performance: Resolvers are cached by Newtonsoft.Json, so they avoid repetitive reflection calls that converters might make on each serialization.
Here's a quick example of a MultiLangContractResolver for comparison:
public class MultiLangContractResolver : DefaultContractResolver { private readonly Language _targetLang; public MultiLangContractResolver(Language targetLang) { _targetLang = targetLang; } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); // Check for a translated name attribute on the field/property var translatedAttr = member.GetCustomAttributes<TranslatedFieldName>() .FirstOrDefault(attr => attr.Lang == _targetLang); if (translatedAttr != null) { property.PropertyName = translatedAttr.Name; } return property; } }
To use it, you'd configure your serialization settings once:
var germanSerializationSettings = new JsonSerializerSettings { ContractResolver = new MultiLangContractResolver(Language.DE) }; string germanJson = JsonConvert.SerializeObject(to, germanSerializationSettings);
Recommendation:
If you're only handling a handful of specific classes, stick with JsonConverter for simplicity. If multi-language serialization is a core requirement across many classes in your app, go with ContractResolver for better maintainability and performance.
内容的提问来源于stack exchange,提问作者koks der drache

