您的问题与来自的问题类似JsonSerializer.CreateDefault().Populate(..) 重置我的值:您想要填充一个预先存在的集合,特别是一个Dictionary<int, T>
对于一些T
,并填充预先存在的值。不幸的是,对于字典,Json.NET 将replace值而不是填充它们,如中所示JsonSerializerInternalReader.PopulateDictionary()它只是将值反序列化为适当的类型,并将其设置为字典。
要解决此限制,您可以创建一个custom JsonConverter for Dictionary<TKey, TValue>
when TKey
是一个原始类型并且TValue
是一种复杂类型,它将传入的 JSON 键/值对合并到预先存在的字典中。下面的转换器可以解决这个问题:
public class DictionaryMergeConverter : JsonConverter
{
static readonly IContractResolver defaultResolver = JsonSerializer.CreateDefault().ContractResolver;
readonly IContractResolver resolver = defaultResolver;
public override bool CanConvert(Type objectType)
{
var keyValueTypes = objectType.GetDictionaryKeyValueType();
if (keyValueTypes == null)
return false;
var keyContract = resolver.ResolveContract(keyValueTypes[0]);
if (!(keyContract is JsonPrimitiveContract))
return false;
var contract = resolver.ResolveContract(keyValueTypes[1]);
return contract is JsonContainerContract;
// Also possibly check whether keyValueTypes[1] is a read-only collection or dictionary.
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.MoveToContentAndAssert().TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
IDictionary dictionary = existingValue as IDictionary ?? (IDictionary)serializer.ContractResolver.ResolveContract(objectType).DefaultCreator();
var keyValueTypes = objectType.GetDictionaryKeyValueType();
while (reader.ReadToContentAndAssert().TokenType != JsonToken.EndObject)
{
switch (reader.TokenType)
{
case JsonToken.PropertyName:
var name = (string)reader.Value;
reader.ReadToContentAndAssert();
// TODO: DateTime keys and enums with overridden names.
var key = (keyValueTypes[0] == typeof(string) ? (object)name : Convert.ChangeType(name, keyValueTypes[0], serializer.Culture));
var value = dictionary.Contains(key) ? dictionary[key] : null;
// TODO:
// - JsonConverter active for valueType, either in contract or in serializer.Converters
// - NullValueHandling, ObjectCreationHandling, PreserveReferencesHandling,
if (value == null)
{
value = serializer.Deserialize(reader, keyValueTypes[1]);
}
else
{
serializer.Populate(reader, value);
}
dictionary[key] = value;
break;
default:
throw new JsonSerializationException(string.Format("Unexpected token {0}", reader.TokenType));
}
}
return dictionary;
}
public override bool CanWrite { get { return false; } }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { throw new NotImplementedException(); }
}
public static partial class JsonExtensions
{
public static JsonReader ReadToContentAndAssert(this JsonReader reader)
{
return reader.ReadAndAssert().MoveToContentAndAssert();
}
public static JsonReader MoveToContentAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (reader.TokenType == JsonToken.None) // Skip past beginning of stream.
reader.ReadAndAssert();
while (reader.TokenType == JsonToken.Comment) // Skip past comments.
reader.ReadAndAssert();
return reader;
}
public static JsonReader ReadAndAssert(this JsonReader reader)
{
if (reader == null)
throw new ArgumentNullException();
if (!reader.Read())
throw new JsonReaderException("Unexpected end of JSON stream.");
return reader;
}
}
public static class TypeExtensions
{
public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
{
while (type != null)
{
yield return type;
type = type.BaseType;
}
}
public static Type[] GetDictionaryKeyValueType(this Type type)
{
return type.BaseTypesAndSelf().Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Dictionary<,>)).Select(t => t.GetGenericArguments()).FirstOrDefault();
}
}
这样做后,您将遇到第二个问题:Json.NET 永远不会使用自定义转换器来填充根对象。要解决此问题,您需要致电JsonConverter.ReadJson()直接从一些实用方法:
public static partial class JsonExtensions
{
public static void PopulateObjectWithConverter(string value, object target, JsonSerializerSettings settings)
{
if (target == null || value == null)
throw new ArgumentNullException();
var serializer = JsonSerializer.CreateDefault(settings);
var converter = serializer.Converters.Where(c => c.CanConvert(target.GetType()) && c.CanRead).FirstOrDefault() ?? serializer.ContractResolver.ResolveContract(target.GetType()).Converter;
using (var jsonReader = new JsonTextReader(new StringReader(value)))
{
if (converter == null)
serializer.Populate(jsonReader, target);
else
{
jsonReader.MoveToContentAndAssert();
var newtarget = converter.ReadJson(jsonReader, target.GetType(), target, serializer);
if (newtarget != target)
throw new JsonException(string.Format("Converter {0} allocated a new object rather than populating the existing object {1}.", converter, value));
}
}
}
}
您现在可以按如下方式填充词典:
var jsonString = JsonConvert.SerializeObject(to_serialize, Formatting.Indented);
var settings = new JsonSerializerSettings
{
Converters = { new DictionaryMergeConverter() },
};
JsonExtensions.PopulateObjectWithConverter(jsonString, to_serialize, settings);
Notes:
-
PreserveReferencesHandling对于是否填充或替换字典值没有影响。相反,此设置控制具有对同一对象的多个引用的序列化图在往返时是否保持其引用拓扑。
-
在你的问题中你写了// works ok with list<Model>
但事实上这是不正确的。当一个List<T>
已填充新值是appended到列表中,所以Assert.AreSame(to_serialize[0], model);
纯粹是靠运气过去的。如果你另外断言Assert.AreSame(1, to_serialize.Count)
它会失败的。
-
虽然转换器适用于原始键,例如string
and int
它可能不适用于需要 JSON 特定转换的键类型,例如enum
or DateTime
.
-
该转换器目前仅适用于Dictionary<TKey, TValue>
并利用此类型实现非泛型的事实IDictionary
界面。它可以扩展到其他字典类型,例如SortedDictionary<TKey,TValue>
如果需要。
演示小提琴here.