DecimalWithoutTrailingZerosConverter 不用于字典键的原因是 Json.NET 不会序列化键 - 它只是将它们转换为字符串。来自docs:
序列化字典时,字典的键被转换为字符串并用作 JSON 对象属性名称。可以通过重写键类型的 ToString() 或实现 TypeConverter 来自定义为键编写的字符串。 TypeConverter 还将支持在反序列化字典时再次将自定义字符串转换回来。
因此,为了获得您需要的输出,您可以按照here 的说明覆盖系统TypeConverter 为decimal - 但我不会真正推荐它,因为这会更改用于decimal 的转换器在您的应用程序的任何地方,都会产生各种无法预料的后果。
另一种方法是为所有实现IDictionary<decimal, TValue> 的字典写一个custom JsonConverter,对于一些TValue:
public class DecimalDictionaryWithoutTrailingZerosConverter : DecimalWithoutTrailingZerosConverterBase
{
public DecimalDictionaryWithoutTrailingZerosConverter(IFormatProvider formatProvider) : base(formatProvider) { }
public override Boolean CanConvert(Type objectType)
{
var types = objectType.GetDictionaryKeyValueTypes().ToList();
return types.Count == 1 && types[0].Key == typeof(Decimal);
}
object ReadJsonGeneric<TValue>(JsonReader reader, Type objectType, IDictionary<decimal, TValue> existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException("Invalid object type " + reader.TokenType);
if (existingValue == null)
{
var contract = serializer.ContractResolver.ResolveContract(objectType);
existingValue = (IDictionary<decimal, TValue>)contract.DefaultCreator();
}
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.Comment:
break;
case JsonToken.PropertyName:
{
var name = reader.Value.ToString();
var key = TokenToDecimal(JsonToken.String, name);
if (!reader.Read())
throw new JsonSerializationException(string.Format("Missing value at path: {0}", reader.Path));
var value = serializer.Deserialize<TValue>(reader);
existingValue.Add(key, value);
}
break;
case JsonToken.EndObject:
return existingValue;
default:
throw new JsonSerializationException(string.Format("Unknown token {0} at path: {1} ", reader.TokenType, reader.Path));
}
}
throw new JsonSerializationException(string.Format("Unclosed object at path: {0}", reader.Path));
}
public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
try
{
var keyValueTypes = objectType.GetDictionaryKeyValueTypes().Single(); // Throws an exception if not exactly one.
var method = GetType().GetMethod("ReadJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(new[] { keyValueTypes.Value });
return genericMethod.Invoke(this, new object[] { reader, objectType, existingValue, serializer });
}
catch (Exception ex)
{
if (ex is JsonException)
throw;
// Wrap the TypeInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to deserialize " + objectType, ex);
}
}
void WriteJsonGeneric<TValue>(JsonWriter writer, IDictionary<decimal, TValue> value, JsonSerializer serializer)
{
writer.WriteStartObject();
foreach (var pair in value)
{
writer.WritePropertyName(DecimalToToken(pair.Key));
serializer.Serialize(writer, pair.Value);
}
writer.WriteEndObject();
}
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
{
try
{
var keyValueTypes = value.GetType().GetDictionaryKeyValueTypes().Single(); // Throws an exception if not exactly one.
var method = GetType().GetMethod("WriteJsonGeneric", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(new[] { keyValueTypes.Value });
genericMethod.Invoke(this, new object[] { writer, value, serializer });
}
catch (Exception ex)
{
if (ex is JsonException)
throw;
// Wrap the TypeInvocationException in a JsonSerializerException
throw new JsonSerializationException("Failed to serialize " + value, ex);
}
}
}
public class DecimalWithoutTrailingZerosConverter : DecimalWithoutTrailingZerosConverterBase
{
public DecimalWithoutTrailingZerosConverter(IFormatProvider formatProvider) : base(formatProvider) { }
public override Boolean CanConvert(Type objectType)
{
return objectType == typeof(Decimal);
}
public override Object ReadJson(JsonReader reader, Type objectType, Object existingValue, JsonSerializer serializer)
{
return TokenToDecimal(reader.TokenType, reader.Value);
}
public override void WriteJson(JsonWriter writer, Object value, JsonSerializer serializer)
{
writer.WriteValue(DecimalToToken(value));
}
}
public abstract class DecimalWithoutTrailingZerosConverterBase : JsonConverter
{
private readonly IFormatProvider formatProvider;
public DecimalWithoutTrailingZerosConverterBase(IFormatProvider formatProvider)
{
this.formatProvider = formatProvider;
}
protected string DecimalToToken(decimal value)
{
return value.ToString("G29", formatProvider);
}
protected string DecimalToToken(object value)
{
if (value is decimal)
{
return DecimalToToken((Decimal)value);
}
else
{
throw new JsonSerializationException("Expected date object value.");
}
}
protected decimal TokenToDecimal(JsonToken tokenType, object value)
{
if (tokenType != JsonToken.String)
throw new JsonSerializationException("Wrong Token Type");
return Convert.ToDecimal(value, formatProvider);
}
}
public static class TypeExtensions
{
/// <summary>
/// Return all interfaces implemented by the incoming type as well as the type itself if it is an interface.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static IEnumerable<Type> GetInterfacesAndSelf(this Type type)
{
if (type == null)
throw new ArgumentNullException();
if (type.IsInterface)
return new[] { type }.Concat(type.GetInterfaces());
else
return type.GetInterfaces();
}
public static IEnumerable<KeyValuePair<Type, Type>> GetDictionaryKeyValueTypes(this Type type)
{
foreach (Type intType in type.GetInterfacesAndSelf())
{
if (intType.IsGenericType
&& intType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
var args = intType.GetGenericArguments();
if (args.Length == 2)
yield return new KeyValuePair<Type, Type>(args[0], args[1]);
}
}
}
}
然后像这样使用它:
var culture = new CultureInfo("de-DE");
var settings = new JsonSerializerSettings
{
Converters = new JsonConverter[] { new DecimalWithoutTrailingZerosConverter(culture), new DecimalDictionaryWithoutTrailingZerosConverter(culture) },
Formatting = Formatting.Indented,
};
var json = JsonConvert.SerializeObject(rateCountEvent, settings);
注意所有类型的十进制字典都使用一个转换器。另请注意,转换器使用existingValue(如果存在)。因此,如果您的 RateCountEvent 中的构造函数分配了一个排序字典而不是一个字典,则排序字典将被填充。
示例fiddle。
顺便说一句,您可能希望扩展您的 DecimalWithoutTrailingZerosConverter 以处理 decimal? 和 decimal。