180 lines
8.4 KiB
C#
180 lines
8.4 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
|
|
namespace FullSerializer.Internal {
|
|
// While the generic IEnumerable converter can handle dictionaries, we
|
|
// process them separately here because we support a few more advanced
|
|
// use-cases with dictionaries, such as inline strings. Further, dictionary
|
|
// processing in general is a bit more advanced because a few of the
|
|
// collection implementations are buggy.
|
|
public class fsDictionaryConverter : fsConverter {
|
|
public override bool CanProcess(Type type) {
|
|
return typeof(IDictionary).IsAssignableFrom(type);
|
|
}
|
|
|
|
public override object CreateInstance(fsData data, Type storageType) {
|
|
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
|
|
}
|
|
|
|
public override fsResult TryDeserialize(fsData data, ref object instance_, Type storageType) {
|
|
var instance = (IDictionary)instance_;
|
|
var result = fsResult.Success;
|
|
|
|
Type keyStorageType, valueStorageType;
|
|
GetKeyValueTypes(instance.GetType(), out keyStorageType, out valueStorageType);
|
|
|
|
if (data.IsList) {
|
|
var list = data.AsList;
|
|
for (int i = 0; i < list.Count; ++i) {
|
|
var item = list[i];
|
|
|
|
fsData keyData, valueData;
|
|
if ((result += CheckType(item, fsDataType.Object)).Failed) return result;
|
|
if ((result += CheckKey(item, "Key", out keyData)).Failed) return result;
|
|
if ((result += CheckKey(item, "Value", out valueData)).Failed) return result;
|
|
|
|
object keyInstance = null, valueInstance = null;
|
|
if ((result += Serializer.TryDeserialize(keyData, keyStorageType, ref keyInstance)).Failed) return result;
|
|
if ((result += Serializer.TryDeserialize(valueData, valueStorageType, ref valueInstance)).Failed) return result;
|
|
|
|
AddItemToDictionary(instance, keyInstance, valueInstance);
|
|
}
|
|
}
|
|
else if (data.IsDictionary) {
|
|
foreach (var entry in data.AsDictionary) {
|
|
if (fsSerializer.IsReservedKeyword(entry.Key)) continue;
|
|
|
|
fsData keyData = new fsData(entry.Key), valueData = entry.Value;
|
|
object keyInstance = null, valueInstance = null;
|
|
|
|
if ((result += Serializer.TryDeserialize(keyData, keyStorageType, ref keyInstance)).Failed) return result;
|
|
if ((result += Serializer.TryDeserialize(valueData, valueStorageType, ref valueInstance)).Failed) return result;
|
|
|
|
AddItemToDictionary(instance, keyInstance, valueInstance);
|
|
}
|
|
}
|
|
else {
|
|
return FailExpectedType(data, fsDataType.Array, fsDataType.Object);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public override fsResult TrySerialize(object instance_, out fsData serialized, Type storageType) {
|
|
serialized = fsData.Null;
|
|
|
|
var result = fsResult.Success;
|
|
|
|
var instance = (IDictionary)instance_;
|
|
|
|
Type keyStorageType, valueStorageType;
|
|
GetKeyValueTypes(instance.GetType(), out keyStorageType, out valueStorageType);
|
|
|
|
// No other way to iterate dictionaries and still have access to the
|
|
// key/value info
|
|
IDictionaryEnumerator enumerator = instance.GetEnumerator();
|
|
|
|
bool allStringKeys = true;
|
|
var serializedKeys = new List<fsData>(instance.Count);
|
|
var serializedValues = new List<fsData>(instance.Count);
|
|
while (enumerator.MoveNext()) {
|
|
fsData keyData, valueData;
|
|
if ((result += Serializer.TrySerialize(keyStorageType, enumerator.Key, out keyData)).Failed) return result;
|
|
if ((result += Serializer.TrySerialize(valueStorageType, enumerator.Value, out valueData)).Failed) return result;
|
|
|
|
serializedKeys.Add(keyData);
|
|
serializedValues.Add(valueData);
|
|
|
|
allStringKeys &= keyData.IsString;
|
|
}
|
|
|
|
if (allStringKeys) {
|
|
serialized = fsData.CreateDictionary();
|
|
var serializedDictionary = serialized.AsDictionary;
|
|
|
|
for (int i = 0; i < serializedKeys.Count; ++i) {
|
|
fsData key = serializedKeys[i];
|
|
fsData value = serializedValues[i];
|
|
serializedDictionary[key.AsString] = value;
|
|
}
|
|
}
|
|
else {
|
|
serialized = fsData.CreateList(serializedKeys.Count);
|
|
var serializedList = serialized.AsList;
|
|
|
|
for (int i = 0; i < serializedKeys.Count; ++i) {
|
|
fsData key = serializedKeys[i];
|
|
fsData value = serializedValues[i];
|
|
|
|
var container = new Dictionary<string, fsData>();
|
|
container["Key"] = key;
|
|
container["Value"] = value;
|
|
serializedList.Add(new fsData(container));
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private fsResult AddItemToDictionary(IDictionary dictionary, object key, object value) {
|
|
// Because we're operating through the IDictionary interface by
|
|
// default (and not the generic one), we normally send items through
|
|
// IDictionary.Add(object, object). This works fine in the general
|
|
// case, except that the add method verifies that it's parameter
|
|
// types are proper types. However, mono is buggy and these type
|
|
// checks do not consider null a subtype of the parameter types, and
|
|
// exceptions get thrown. So, we have to special case adding null
|
|
// items via the generic functions (which do not do the null check),
|
|
// which is slow and messy.
|
|
//
|
|
// An example of a collection that fails deserialization without this
|
|
// method is `new SortedList<int, string> { { 0, null } }`.
|
|
// (SortedDictionary is fine because it properly handles null
|
|
// values).
|
|
if (key == null || value == null) {
|
|
// Life would be much easier if we had MakeGenericType available,
|
|
// but we don't. So we're going to find the correct generic
|
|
// KeyValuePair type via a bit of trickery. All dictionaries
|
|
// extend ICollection<KeyValuePair<TKey, TValue>>, so we just
|
|
// fetch the ICollection<> type with the proper generic
|
|
// arguments, and then we take the KeyValuePair<> generic
|
|
// argument, and whola! we have our proper generic type.
|
|
|
|
var collectionType = fsReflectionUtility.GetInterface(dictionary.GetType(), typeof(ICollection<>));
|
|
if (collectionType == null) {
|
|
return fsResult.Warn(dictionary.GetType() + " does not extend ICollection");
|
|
}
|
|
|
|
var keyValuePairType = collectionType.GetGenericArguments()[0];
|
|
object keyValueInstance = Activator.CreateInstance(keyValuePairType, key, value);
|
|
MethodInfo add = collectionType.GetFlattenedMethod("Add");
|
|
add.Invoke(dictionary, new object[] { keyValueInstance });
|
|
return fsResult.Success;
|
|
}
|
|
|
|
// We use the inline set methods instead of dictionary.Add;
|
|
// dictionary.Add will throw an exception if the key already exists.
|
|
dictionary[key] = value;
|
|
return fsResult.Success;
|
|
}
|
|
|
|
private static void GetKeyValueTypes(Type dictionaryType, out Type keyStorageType, out Type valueStorageType) {
|
|
// All dictionaries extend IDictionary<TKey, TValue>, so we just
|
|
// fetch the generic arguments from it
|
|
var interfaceType = fsReflectionUtility.GetInterface(dictionaryType, typeof(IDictionary<,>));
|
|
if (interfaceType != null) {
|
|
var genericArgs = interfaceType.GetGenericArguments();
|
|
keyStorageType = genericArgs[0];
|
|
valueStorageType = genericArgs[1];
|
|
}
|
|
else {
|
|
// Fetching IDictionary<,> failed... we have to encode full type
|
|
// information :(
|
|
keyStorageType = typeof(object);
|
|
valueStorageType = typeof(object);
|
|
}
|
|
}
|
|
}
|
|
} |