844 lines
40 KiB
C#
844 lines
40 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using FullSerializer.Internal;
|
|
|
|
#if !UNITY_EDITOR && UNITY_WSA
|
|
// For System.Reflection.TypeExtensions
|
|
using System.Reflection;
|
|
#endif
|
|
|
|
namespace FullSerializer {
|
|
public class fsSerializer {
|
|
#region Keys
|
|
private static HashSet<string> _reservedKeywords;
|
|
static fsSerializer() {
|
|
_reservedKeywords = new HashSet<string> {
|
|
Key_ObjectReference,
|
|
Key_ObjectDefinition,
|
|
Key_InstanceType,
|
|
Key_Version,
|
|
Key_Content
|
|
};
|
|
}
|
|
/// <summary>
|
|
/// Returns true if the given key is a special keyword that full serializer uses to
|
|
/// add additional metadata on top of the emitted JSON.
|
|
/// </summary>
|
|
public static bool IsReservedKeyword(string key) {
|
|
return _reservedKeywords.Contains(key);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is an object reference in part of a cyclic graph.
|
|
/// </summary>
|
|
private static readonly string Key_ObjectReference = string.Format("{0}ref", fsGlobalConfig.InternalFieldPrefix);
|
|
|
|
/// <summary>
|
|
/// This is an object definition, as part of a cyclic graph.
|
|
/// </summary>
|
|
private static readonly string Key_ObjectDefinition = string.Format("{0}id", fsGlobalConfig.InternalFieldPrefix);
|
|
|
|
/// <summary>
|
|
/// This specifies the actual type of an object (the instance type was different from
|
|
/// the field type).
|
|
/// </summary>
|
|
private static readonly string Key_InstanceType = string.Format("{0}type", fsGlobalConfig.InternalFieldPrefix);
|
|
|
|
/// <summary>
|
|
/// The version string for the serialized data.
|
|
/// </summary>
|
|
private static readonly string Key_Version = string.Format("{0}version", fsGlobalConfig.InternalFieldPrefix);
|
|
|
|
/// <summary>
|
|
/// If we have to add metadata but the original serialized state was not a dictionary,
|
|
/// then this will contain the original data.
|
|
/// </summary>
|
|
private static readonly string Key_Content = string.Format("{0}content", fsGlobalConfig.InternalFieldPrefix);
|
|
|
|
private static bool IsObjectReference(fsData data) {
|
|
if (data.IsDictionary == false) return false;
|
|
return data.AsDictionary.ContainsKey(Key_ObjectReference);
|
|
}
|
|
private static bool IsObjectDefinition(fsData data) {
|
|
if (data.IsDictionary == false) return false;
|
|
return data.AsDictionary.ContainsKey(Key_ObjectDefinition);
|
|
}
|
|
private static bool IsVersioned(fsData data) {
|
|
if (data.IsDictionary == false) return false;
|
|
return data.AsDictionary.ContainsKey(Key_Version);
|
|
}
|
|
private static bool IsTypeSpecified(fsData data) {
|
|
if (data.IsDictionary == false) return false;
|
|
return data.AsDictionary.ContainsKey(Key_InstanceType);
|
|
}
|
|
private static bool IsWrappedData(fsData data) {
|
|
if (data.IsDictionary == false) return false;
|
|
return data.AsDictionary.ContainsKey(Key_Content);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Strips all deserialization metadata from the object, like $type and $content fields.
|
|
/// </summary>
|
|
/// <remarks>After making this call, you will *not* be able to deserialize the same object instance. The metadata is
|
|
/// strictly necessary for deserialization!</remarks>
|
|
public static void StripDeserializationMetadata(ref fsData data) {
|
|
if (data.IsDictionary && data.AsDictionary.ContainsKey(Key_Content)) {
|
|
data = data.AsDictionary[Key_Content];
|
|
}
|
|
|
|
if (data.IsDictionary) {
|
|
var dict = data.AsDictionary;
|
|
dict.Remove(Key_ObjectReference);
|
|
dict.Remove(Key_ObjectDefinition);
|
|
dict.Remove(Key_InstanceType);
|
|
dict.Remove(Key_Version);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This function converts legacy serialization data into the new format, so that
|
|
/// the import process can be unified and ignore the old format.
|
|
/// </summary>
|
|
private static void ConvertLegacyData(ref fsData data) {
|
|
if (data.IsDictionary == false) return;
|
|
|
|
var dict = data.AsDictionary;
|
|
|
|
// fast-exit: metadata never had more than two items
|
|
if (dict.Count > 2) return;
|
|
|
|
// Key strings used in the legacy system
|
|
string referenceIdString = "ReferenceId";
|
|
string sourceIdString = "SourceId";
|
|
string sourceDataString = "Data";
|
|
string typeString = "Type";
|
|
string typeDataString = "Data";
|
|
|
|
// type specifier
|
|
if (dict.Count == 2 && dict.ContainsKey(typeString) && dict.ContainsKey(typeDataString)) {
|
|
data = dict[typeDataString];
|
|
EnsureDictionary(data);
|
|
ConvertLegacyData(ref data);
|
|
|
|
data.AsDictionary[Key_InstanceType] = dict[typeString];
|
|
}
|
|
|
|
// object definition
|
|
else if (dict.Count == 2 && dict.ContainsKey(sourceIdString) && dict.ContainsKey(sourceDataString)) {
|
|
data = dict[sourceDataString];
|
|
EnsureDictionary(data);
|
|
ConvertLegacyData(ref data);
|
|
|
|
data.AsDictionary[Key_ObjectDefinition] = dict[sourceIdString];
|
|
}
|
|
|
|
// object reference
|
|
else if (dict.Count == 1 && dict.ContainsKey(referenceIdString)) {
|
|
data = fsData.CreateDictionary();
|
|
data.AsDictionary[Key_ObjectReference] = dict[referenceIdString];
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Utility Methods
|
|
private static void Invoke_OnBeforeSerialize(List<fsObjectProcessor> processors, Type storageType, object instance) {
|
|
for (int i = 0; i < processors.Count; ++i) {
|
|
processors[i].OnBeforeSerialize(storageType, instance);
|
|
}
|
|
}
|
|
private static void Invoke_OnAfterSerialize(List<fsObjectProcessor> processors, Type storageType, object instance, ref fsData data) {
|
|
// We run the after calls in reverse order; this significantly reduces the interaction burden between
|
|
// multiple processors - it makes each one much more independent and ignorant of the other ones.
|
|
|
|
for (int i = processors.Count - 1; i >= 0; --i) {
|
|
processors[i].OnAfterSerialize(storageType, instance, ref data);
|
|
}
|
|
}
|
|
private static void Invoke_OnBeforeDeserialize(List<fsObjectProcessor> processors, Type storageType, ref fsData data) {
|
|
for (int i = 0; i < processors.Count; ++i) {
|
|
processors[i].OnBeforeDeserialize(storageType, ref data);
|
|
}
|
|
}
|
|
private static void Invoke_OnBeforeDeserializeAfterInstanceCreation(List<fsObjectProcessor> processors, Type storageType, object instance, ref fsData data) {
|
|
for (int i = 0; i < processors.Count; ++i) {
|
|
processors[i].OnBeforeDeserializeAfterInstanceCreation(storageType, instance, ref data);
|
|
}
|
|
}
|
|
private static void Invoke_OnAfterDeserialize(List<fsObjectProcessor> processors, Type storageType, object instance) {
|
|
for (int i = processors.Count - 1; i >= 0; --i) {
|
|
processors[i].OnAfterDeserialize(storageType, instance);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Ensures that the data is a dictionary. If it is not, then it is wrapped inside of one.
|
|
/// </summary>
|
|
private static void EnsureDictionary(fsData data) {
|
|
if (data.IsDictionary == false) {
|
|
var existingData = data.Clone();
|
|
data.BecomeDictionary();
|
|
data.AsDictionary[Key_Content] = existingData;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This manages instance writing so that we do not write unnecessary $id fields. We
|
|
/// only need to write out an $id field when there is a corresponding $ref field. This is able
|
|
/// to write $id references lazily because the fsData instance is not actually written out to text
|
|
/// until we have entirely finished serializing it.
|
|
/// </summary>
|
|
internal class fsLazyCycleDefinitionWriter {
|
|
private Dictionary<int, fsData> _pendingDefinitions = new Dictionary<int, fsData>();
|
|
private HashSet<int> _references = new HashSet<int>();
|
|
|
|
public void WriteDefinition(int id, fsData data) {
|
|
if (_references.Contains(id)) {
|
|
EnsureDictionary(data);
|
|
data.AsDictionary[Key_ObjectDefinition] = new fsData(id.ToString());
|
|
}
|
|
|
|
else {
|
|
_pendingDefinitions[id] = data;
|
|
}
|
|
}
|
|
|
|
public void WriteReference(int id, Dictionary<string, fsData> dict) {
|
|
// Write the actual definition if necessary
|
|
if (_pendingDefinitions.ContainsKey(id)) {
|
|
var data = _pendingDefinitions[id];
|
|
EnsureDictionary(data);
|
|
data.AsDictionary[Key_ObjectDefinition] = new fsData(id.ToString());
|
|
_pendingDefinitions.Remove(id);
|
|
}
|
|
else {
|
|
_references.Add(id);
|
|
}
|
|
|
|
// Write the reference
|
|
dict[Key_ObjectReference] = new fsData(id.ToString());
|
|
}
|
|
|
|
public void Clear() {
|
|
_pendingDefinitions.Clear();
|
|
_references.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converter type to converter instance lookup table. This could likely be stored inside
|
|
// of _cachedConverters, but there is a semantic difference because _cachedConverters goes
|
|
/// from serialized type to converter.
|
|
/// </summary>
|
|
private Dictionary<Type, fsBaseConverter> _cachedConverterTypeInstances;
|
|
|
|
/// <summary>
|
|
/// A cache from type to it's converter.
|
|
/// </summary>
|
|
private Dictionary<Type, fsBaseConverter> _cachedConverters;
|
|
|
|
/// <summary>
|
|
/// A cache from type to the set of processors that are interested in it.
|
|
/// </summary>
|
|
private Dictionary<Type, List<fsObjectProcessor>> _cachedProcessors;
|
|
|
|
/// <summary>
|
|
/// Converters that can be used for type registration.
|
|
/// </summary>
|
|
private readonly List<fsConverter> _availableConverters;
|
|
|
|
/// <summary>
|
|
/// Direct converters (optimized _converters). We use these so we don't have to
|
|
/// perform a scan through every item in _converters and can instead just do an O(1)
|
|
/// lookup. This is potentially important to perf when there are a ton of direct
|
|
/// converters.
|
|
/// </summary>
|
|
private readonly Dictionary<Type, fsDirectConverter> _availableDirectConverters;
|
|
|
|
/// <summary>
|
|
/// Processors that are available.
|
|
/// </summary>
|
|
private readonly List<fsObjectProcessor> _processors;
|
|
|
|
/// <summary>
|
|
/// Reference manager for cycle detection.
|
|
/// </summary>
|
|
private readonly fsCyclicReferenceManager _references;
|
|
private readonly fsLazyCycleDefinitionWriter _lazyReferenceWriter;
|
|
|
|
public fsSerializer() {
|
|
_cachedConverterTypeInstances = new Dictionary<Type, fsBaseConverter>();
|
|
_cachedConverters = new Dictionary<Type, fsBaseConverter>();
|
|
_cachedProcessors = new Dictionary<Type, List<fsObjectProcessor>>();
|
|
|
|
_references = new fsCyclicReferenceManager();
|
|
_lazyReferenceWriter = new fsLazyCycleDefinitionWriter();
|
|
|
|
// note: The order here is important. Items at the beginning of this
|
|
// list will be used before converters at the end. Converters
|
|
// added via AddConverter() are added to the front of the list.
|
|
_availableConverters = new List<fsConverter> {
|
|
new fsNullableConverter { Serializer = this },
|
|
new fsGuidConverter { Serializer = this },
|
|
new fsTypeConverter { Serializer = this },
|
|
new fsDateConverter { Serializer = this },
|
|
new fsEnumConverter { Serializer = this },
|
|
new fsPrimitiveConverter { Serializer = this },
|
|
new fsArrayConverter { Serializer = this },
|
|
new fsDictionaryConverter { Serializer = this },
|
|
new fsIEnumerableConverter { Serializer = this },
|
|
new fsKeyValuePairConverter { Serializer = this },
|
|
new fsWeakReferenceConverter { Serializer = this },
|
|
new fsReflectedConverter { Serializer = this }
|
|
};
|
|
_availableDirectConverters = new Dictionary<Type, fsDirectConverter>();
|
|
|
|
_processors = new List<fsObjectProcessor>() {
|
|
new fsSerializationCallbackProcessor()
|
|
};
|
|
|
|
#if !NO_UNITY
|
|
_processors.Add(new fsSerializationCallbackReceiverProcessor());
|
|
#endif
|
|
|
|
Context = new fsContext();
|
|
Config = new fsConfig();
|
|
|
|
// Register the converters from the registrar
|
|
foreach (var converterType in fsConverterRegistrar.Converters) {
|
|
AddConverter((fsBaseConverter)Activator.CreateInstance(converterType));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A context object that fsConverters can use to customize how they operate.
|
|
/// </summary>
|
|
public fsContext Context;
|
|
|
|
/// <summary>
|
|
/// Configuration options. Also see fsGlobalConfig.
|
|
/// </summary>
|
|
public fsConfig Config;
|
|
|
|
/// <summary>
|
|
/// Add a new processor to the serializer. Multiple processors can run at the same time in the
|
|
/// same order they were added in.
|
|
/// </summary>
|
|
/// <param name="processor">The processor to add.</param>
|
|
public void AddProcessor(fsObjectProcessor processor) {
|
|
_processors.Add(processor);
|
|
|
|
// We need to reset our cached processor set, as it could be invalid with the new
|
|
// processor. Ideally, _cachedProcessors should be empty (as the user should fully setup
|
|
// the serializer before actually using it), but there is no guarantee.
|
|
_cachedProcessors = new Dictionary<Type, List<fsObjectProcessor>>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all processors which derive from TProcessor.
|
|
/// </summary>
|
|
public void RemoveProcessor<TProcessor>() {
|
|
int i = 0;
|
|
while (i < _processors.Count) {
|
|
if (_processors[i] is TProcessor) {
|
|
_processors.RemoveAt(i);
|
|
}
|
|
else {
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// We need to reset our cached processor set, as it could be invalid with the new
|
|
// processor. Ideally, _cachedProcessors should be empty (as the user should fully setup
|
|
// the serializer before actually using it), but there is no guarantee.
|
|
_cachedProcessors = new Dictionary<Type, List<fsObjectProcessor>>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fetches all of the processors for the given type.
|
|
/// </summary>
|
|
private List<fsObjectProcessor> GetProcessors(Type type) {
|
|
List<fsObjectProcessor> processors;
|
|
|
|
// Check to see if the user has defined a custom processor for the type. If they
|
|
// have, then we don't need to scan through all of the processor to check which
|
|
// one can process the type; instead, we directly use the specified processor.
|
|
var attr = fsPortableReflection.GetAttribute<fsObjectAttribute>(type);
|
|
if (attr != null && attr.Processor != null) {
|
|
var processor = (fsObjectProcessor)Activator.CreateInstance(attr.Processor);
|
|
processors = new List<fsObjectProcessor>();
|
|
processors.Add(processor);
|
|
_cachedProcessors[type] = processors;
|
|
}
|
|
|
|
else if (_cachedProcessors.TryGetValue(type, out processors) == false) {
|
|
processors = new List<fsObjectProcessor>();
|
|
|
|
for (int i = 0; i < _processors.Count; ++i) {
|
|
var processor = _processors[i];
|
|
if (processor.CanProcess(type)) {
|
|
processors.Add(processor);
|
|
}
|
|
}
|
|
|
|
_cachedProcessors[type] = processors;
|
|
}
|
|
|
|
return processors;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Adds a new converter that can be used to customize how an object is serialized and
|
|
/// deserialized.
|
|
/// </summary>
|
|
public void AddConverter(fsBaseConverter converter) {
|
|
if (converter.Serializer != null) {
|
|
throw new InvalidOperationException("Cannot add a single converter instance to " +
|
|
"multiple fsConverters -- please construct a new instance for " + converter);
|
|
}
|
|
|
|
// TODO: wrap inside of a ConverterManager so we can control _converters and _cachedConverters lifetime
|
|
if (converter is fsDirectConverter) {
|
|
var directConverter = (fsDirectConverter)converter;
|
|
_availableDirectConverters[directConverter.ModelType] = directConverter;
|
|
}
|
|
else if (converter is fsConverter) {
|
|
_availableConverters.Insert(0, (fsConverter)converter);
|
|
}
|
|
else {
|
|
throw new InvalidOperationException("Unable to add converter " + converter +
|
|
"; the type association strategy is unknown. Please use either " +
|
|
"fsDirectConverter or fsConverter as your base type.");
|
|
}
|
|
|
|
converter.Serializer = this;
|
|
|
|
// We need to reset our cached converter set, as it could be invalid with the new
|
|
// converter. Ideally, _cachedConverters should be empty (as the user should fully setup
|
|
// the serializer before actually using it), but there is no guarantee.
|
|
_cachedConverters = new Dictionary<Type, fsBaseConverter>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fetches a converter that can serialize/deserialize the given type.
|
|
/// </summary>
|
|
private fsBaseConverter GetConverter(Type type, Type overrideConverterType) {
|
|
// Use an override converter type instead if that's what the user has requested.
|
|
if (overrideConverterType != null) {
|
|
fsBaseConverter overrideConverter;
|
|
if (_cachedConverterTypeInstances.TryGetValue(overrideConverterType, out overrideConverter) == false) {
|
|
overrideConverter = (fsBaseConverter)Activator.CreateInstance(overrideConverterType);
|
|
overrideConverter.Serializer = this;
|
|
_cachedConverterTypeInstances[overrideConverterType] = overrideConverter;
|
|
}
|
|
|
|
return overrideConverter;
|
|
}
|
|
|
|
// Try to lookup an existing converter.
|
|
fsBaseConverter converter;
|
|
if (_cachedConverters.TryGetValue(type, out converter)) {
|
|
return converter;
|
|
}
|
|
|
|
// Check to see if the user has defined a custom converter for the type. If they
|
|
// have, then we don't need to scan through all of the converters to check which
|
|
// one can process the type; instead, we directly use the specified converter.
|
|
{
|
|
var attr = fsPortableReflection.GetAttribute<fsObjectAttribute>(type);
|
|
if (attr != null && attr.Converter != null) {
|
|
converter = (fsBaseConverter)Activator.CreateInstance(attr.Converter);
|
|
converter.Serializer = this;
|
|
return _cachedConverters[type] = converter;
|
|
}
|
|
}
|
|
|
|
// Check for a [fsForward] attribute.
|
|
{
|
|
var attr = fsPortableReflection.GetAttribute<fsForwardAttribute>(type);
|
|
if (attr != null) {
|
|
converter = new fsForwardConverter(attr);
|
|
converter.Serializer = this;
|
|
return _cachedConverters[type] = converter;
|
|
}
|
|
}
|
|
|
|
|
|
// There is no specific converter specified; try all of the general ones to see
|
|
// which ones matches.
|
|
if (_cachedConverters.TryGetValue(type, out converter) == false) {
|
|
if (_availableDirectConverters.ContainsKey(type)) {
|
|
converter = _availableDirectConverters[type];
|
|
return _cachedConverters[type] = converter;
|
|
}
|
|
else {
|
|
for (int i = 0; i < _availableConverters.Count; ++i) {
|
|
if (_availableConverters[i].CanProcess(type)) {
|
|
converter = _availableConverters[i];
|
|
return _cachedConverters[type] = converter;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
throw new InvalidOperationException("Internal error -- could not find a converter for " + type);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper method that simply forwards the call to TrySerialize(typeof(T), instance, out data);
|
|
/// </summary>
|
|
public fsResult TrySerialize<T>(T instance, out fsData data) {
|
|
return TrySerialize(typeof(T), instance, out data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generic wrapper around TryDeserialize that simply forwards the call.
|
|
/// </summary>
|
|
public fsResult TryDeserialize<T>(fsData data, ref T instance) {
|
|
object boxed = instance;
|
|
var fail = TryDeserialize(data, typeof(T), ref boxed);
|
|
if (fail.Succeeded) {
|
|
instance = (T)boxed;
|
|
}
|
|
return fail;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize the given value.
|
|
/// </summary>
|
|
/// <param name="storageType">The type of field/property that stores the object instance. This is
|
|
/// important particularly for inheritance, as a field storing an IInterface instance
|
|
/// should have type information included.</param>
|
|
/// <param name="instance">The actual object instance to serialize.</param>
|
|
/// <param name="data">The serialized state of the object.</param>
|
|
/// <returns>If serialization was successful.</returns>
|
|
public fsResult TrySerialize(Type storageType, object instance, out fsData data) {
|
|
return TrySerialize(storageType, null, instance, out data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize the given value.
|
|
/// </summary>
|
|
/// <param name="storageType">The type of field/property that stores the object instance. This is
|
|
/// important particularly for inheritance, as a field storing an IInterface instance
|
|
/// should have type information included.</param>
|
|
/// <param name="overrideConverterType">An fsBaseConverter derived type that will be used to serialize
|
|
/// the object instead of the converter found via the normal discovery mechanisms.</param>
|
|
/// <param name="instance">The actual object instance to serialize.</param>
|
|
/// <param name="data">The serialized state of the object.</param>
|
|
/// <returns>If serialization was successful.</returns>
|
|
public fsResult TrySerialize(Type storageType, Type overrideConverterType, object instance, out fsData data) {
|
|
var processors = GetProcessors(instance == null ? storageType : instance.GetType());
|
|
|
|
Invoke_OnBeforeSerialize(processors, storageType, instance);
|
|
|
|
// We always serialize null directly as null
|
|
if (ReferenceEquals(instance, null)) {
|
|
data = new fsData();
|
|
Invoke_OnAfterSerialize(processors, storageType, instance, ref data);
|
|
return fsResult.Success;
|
|
}
|
|
|
|
var result = InternalSerialize_1_ProcessCycles(storageType, overrideConverterType, instance, out data);
|
|
Invoke_OnAfterSerialize(processors, storageType, instance, ref data);
|
|
return result;
|
|
}
|
|
|
|
private fsResult InternalSerialize_1_ProcessCycles(Type storageType, Type overrideConverterType, object instance, out fsData data) {
|
|
// We have an object definition to serialize.
|
|
try {
|
|
// Note that we enter the reference group at the beginning of serialization so that we support
|
|
// references that are at equal serialization levels, not just nested serialization levels, within
|
|
// the given subobject. A prime example is serialization a list of references.
|
|
_references.Enter();
|
|
|
|
// This type does not need cycle support.
|
|
var converter = GetConverter(instance.GetType(), overrideConverterType);
|
|
if (converter.RequestCycleSupport(instance.GetType()) == false) {
|
|
return InternalSerialize_2_Inheritance(storageType, overrideConverterType, instance, out data);
|
|
}
|
|
|
|
// We've already serialized this object instance (or it is pending higher up on the call stack).
|
|
// Just serialize a reference to it to escape the cycle.
|
|
//
|
|
// note: We serialize the int as a string to so that we don't lose any information
|
|
// in a conversion to/from double.
|
|
if (_references.IsReference(instance)) {
|
|
data = fsData.CreateDictionary();
|
|
_lazyReferenceWriter.WriteReference(_references.GetReferenceId(instance), data.AsDictionary);
|
|
return fsResult.Success;
|
|
}
|
|
|
|
// Mark inside the object graph that we've serialized the instance. We do this *before*
|
|
// serialization so that if we get back into this function recursively, it'll already
|
|
// be marked and we can handle the cycle properly without going into an infinite loop.
|
|
_references.MarkSerialized(instance);
|
|
|
|
// We've created the cycle metadata, so we can now serialize the actual object.
|
|
// InternalSerialize will handle inheritance correctly for us.
|
|
var result = InternalSerialize_2_Inheritance(storageType, overrideConverterType, instance, out data);
|
|
if (result.Failed) return result;
|
|
|
|
_lazyReferenceWriter.WriteDefinition(_references.GetReferenceId(instance), data);
|
|
|
|
return result;
|
|
}
|
|
finally {
|
|
if (_references.Exit()) {
|
|
_lazyReferenceWriter.Clear();
|
|
}
|
|
}
|
|
}
|
|
private fsResult InternalSerialize_2_Inheritance(Type storageType, Type overrideConverterType, object instance, out fsData data) {
|
|
// Serialize the actual object with the field type being the same as the object
|
|
// type so that we won't go into an infinite loop.
|
|
var serializeResult = InternalSerialize_3_ProcessVersioning(overrideConverterType, instance, out data);
|
|
if (serializeResult.Failed) return serializeResult;
|
|
|
|
// Do we need to add type information? If the field type and the instance type are different
|
|
// then we will not be able to recover the correct instance type from the field type when
|
|
// we deserialize the object.
|
|
//
|
|
// Note: We allow converters to request that we do *not* add type information.
|
|
if (storageType != instance.GetType() &&
|
|
GetConverter(storageType, overrideConverterType).RequestInheritanceSupport(storageType)) {
|
|
|
|
// Add the inheritance metadata
|
|
EnsureDictionary(data);
|
|
data.AsDictionary[Key_InstanceType] = new fsData(instance.GetType().FullName);
|
|
}
|
|
|
|
return serializeResult;
|
|
}
|
|
|
|
private fsResult InternalSerialize_3_ProcessVersioning(Type overrideConverterType, object instance, out fsData data) {
|
|
// note: We do not have to take a Type parameter here, since at this point in the serialization
|
|
// algorithm inheritance has *always* been handled. If we took a type parameter, it will
|
|
// *always* be equal to instance.GetType(), so why bother taking the parameter?
|
|
|
|
// Check to see if there is versioning information for this type. If so, then we need to serialize it.
|
|
fsOption<fsVersionedType> optionalVersionedType = fsVersionManager.GetVersionedType(instance.GetType());
|
|
if (optionalVersionedType.HasValue) {
|
|
fsVersionedType versionedType = optionalVersionedType.Value;
|
|
|
|
// Serialize the actual object content; we'll just wrap it with versioning metadata here.
|
|
var result = InternalSerialize_4_Converter(overrideConverterType, instance, out data);
|
|
if (result.Failed) return result;
|
|
|
|
// Add the versioning information
|
|
EnsureDictionary(data);
|
|
data.AsDictionary[Key_Version] = new fsData(versionedType.VersionString);
|
|
|
|
return result;
|
|
}
|
|
|
|
// This type has no versioning information -- directly serialize it using the selected converter.
|
|
return InternalSerialize_4_Converter(overrideConverterType, instance, out data);
|
|
}
|
|
private fsResult InternalSerialize_4_Converter(Type overrideConverterType, object instance, out fsData data) {
|
|
var instanceType = instance.GetType();
|
|
return GetConverter(instanceType, overrideConverterType).TrySerialize(instance, out data, instanceType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to deserialize a value from a serialized state.
|
|
/// </summary>
|
|
public fsResult TryDeserialize(fsData data, Type storageType, ref object result) {
|
|
return TryDeserialize(data, storageType, null, ref result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attempts to deserialize a value from a serialized state.
|
|
/// </summary>
|
|
public fsResult TryDeserialize(fsData data, Type storageType, Type overrideConverterType, ref object result) {
|
|
if (data.IsNull) {
|
|
result = null;
|
|
var processors = GetProcessors(storageType);
|
|
Invoke_OnBeforeDeserialize(processors, storageType, ref data);
|
|
Invoke_OnAfterDeserialize(processors, storageType, null);
|
|
return fsResult.Success;
|
|
}
|
|
|
|
// Convert legacy data into modern style data
|
|
ConvertLegacyData(ref data);
|
|
|
|
try {
|
|
// We wrap the entire deserialize call in a reference group so that we can properly
|
|
// deserialize a "parallel" set of references, ie, a list of objects that are cyclic
|
|
// w.r.t. the list
|
|
_references.Enter();
|
|
|
|
List<fsObjectProcessor> processors;
|
|
var r = InternalDeserialize_1_CycleReference(overrideConverterType, data, storageType, ref result, out processors);
|
|
if (r.Succeeded) {
|
|
Invoke_OnAfterDeserialize(processors, storageType, result);
|
|
}
|
|
return r;
|
|
}
|
|
finally {
|
|
_references.Exit();
|
|
}
|
|
}
|
|
|
|
private fsResult InternalDeserialize_1_CycleReference(Type overrideConverterType, fsData data, Type storageType, ref object result, out List<fsObjectProcessor> processors) {
|
|
// We handle object references first because we could be deserializing a cyclic type that is
|
|
// inherited. If that is the case, then if we handle references after inheritances we will try
|
|
// to create an object instance for an abstract/interface type.
|
|
|
|
// While object construction should technically be two-pass, we can do it in
|
|
// one pass because of how serialization happens. We traverse the serialization
|
|
// graph in the same order during serialization and deserialization, so the first
|
|
// time we encounter an object it'll always be the definition. Any times after that
|
|
// it will be a reference. Because of this, if we encounter a reference then we
|
|
// will have *always* already encountered the definition for it.
|
|
if (IsObjectReference(data)) {
|
|
int refId = int.Parse(data.AsDictionary[Key_ObjectReference].AsString);
|
|
result = _references.GetReferenceObject(refId);
|
|
processors = GetProcessors(result.GetType());
|
|
return fsResult.Success;
|
|
}
|
|
|
|
return InternalDeserialize_2_Version(overrideConverterType, data, storageType, ref result, out processors);
|
|
}
|
|
|
|
private fsResult InternalDeserialize_2_Version(Type overrideConverterType, fsData data, Type storageType, ref object result, out List<fsObjectProcessor> processors) {
|
|
if (IsVersioned(data)) {
|
|
// data is versioned, but we might not need to do a migration
|
|
string version = data.AsDictionary[Key_Version].AsString;
|
|
|
|
fsOption<fsVersionedType> versionedType = fsVersionManager.GetVersionedType(storageType);
|
|
if (versionedType.HasValue &&
|
|
versionedType.Value.VersionString != version) {
|
|
|
|
// we have to do a migration
|
|
var deserializeResult = fsResult.Success;
|
|
|
|
List<fsVersionedType> path;
|
|
deserializeResult += fsVersionManager.GetVersionImportPath(version, versionedType.Value, out path);
|
|
if (deserializeResult.Failed) {
|
|
processors = GetProcessors(storageType);
|
|
return deserializeResult;
|
|
}
|
|
|
|
// deserialize as the original type
|
|
deserializeResult += InternalDeserialize_3_Inheritance(overrideConverterType, data, path[0].ModelType, ref result, out processors);
|
|
if (deserializeResult.Failed) return deserializeResult;
|
|
|
|
// TODO: we probably should be invoking object processors all along this pipeline
|
|
for (int i = 1; i < path.Count; ++i) {
|
|
result = path[i].Migrate(result);
|
|
}
|
|
|
|
// Our data contained an object definition ($id) that was added to _references in step 4.
|
|
// However, in case we are doing versioning, it will contain the old version.
|
|
// To make sure future references to this object end up referencing the migrated version,
|
|
// we must update the reference.
|
|
if (IsObjectDefinition(data)) {
|
|
int sourceId = int.Parse(data.AsDictionary[Key_ObjectDefinition].AsString);
|
|
_references.AddReferenceWithId(sourceId, result);
|
|
}
|
|
|
|
processors = GetProcessors(deserializeResult.GetType());
|
|
return deserializeResult;
|
|
}
|
|
}
|
|
|
|
return InternalDeserialize_3_Inheritance(overrideConverterType, data, storageType, ref result, out processors);
|
|
}
|
|
|
|
private fsResult InternalDeserialize_3_Inheritance(Type overrideConverterType, fsData data, Type storageType, ref object result, out List<fsObjectProcessor> processors) {
|
|
var deserializeResult = fsResult.Success;
|
|
|
|
Type objectType = storageType;
|
|
|
|
// If the serialized state contains type information, then we need to make sure to update our
|
|
// objectType and data to the proper values so that when we construct an object instance later
|
|
// and run deserialization we run it on the proper type.
|
|
if (IsTypeSpecified(data)) {
|
|
fsData typeNameData = data.AsDictionary[Key_InstanceType];
|
|
|
|
// we wrap everything in a do while false loop so we can break out it
|
|
do {
|
|
if (typeNameData.IsString == false) {
|
|
deserializeResult.AddMessage(Key_InstanceType + " value must be a string (in " + data + ")");
|
|
break;
|
|
}
|
|
|
|
string typeName = typeNameData.AsString;
|
|
Type type = fsTypeCache.GetType(typeName);
|
|
if (type == null) {
|
|
deserializeResult += fsResult.Fail("Unable to locate specified type \"" + typeName + "\"");
|
|
break;
|
|
}
|
|
|
|
if (storageType.IsAssignableFrom(type) == false) {
|
|
deserializeResult.AddMessage("Ignoring type specifier; a field/property of type " + storageType + " cannot hold an instance of " + type);
|
|
break;
|
|
}
|
|
|
|
objectType = type;
|
|
} while (false);
|
|
}
|
|
|
|
// We wait until here to actually Invoke_OnBeforeDeserialize because we do not
|
|
// have the correct set of processors to invoke until *after* we have resolved
|
|
// the proper type to use for deserialization.
|
|
processors = GetProcessors(objectType);
|
|
|
|
if (deserializeResult.Failed)
|
|
return deserializeResult;
|
|
|
|
Invoke_OnBeforeDeserialize(processors, storageType, ref data);
|
|
|
|
// Construct an object instance if we don't have one already. We also need to construct
|
|
// an instance if the result type is of the wrong type, which may be the case when we
|
|
// have a versioned import graph.
|
|
if (ReferenceEquals(result, null) || result.GetType() != objectType) {
|
|
result = GetConverter(objectType, overrideConverterType).CreateInstance(data, objectType);
|
|
}
|
|
|
|
// We call OnBeforeDeserializeAfterInstanceCreation here because we still want to invoke the
|
|
// method even if the user passed in an existing instance.
|
|
Invoke_OnBeforeDeserializeAfterInstanceCreation(processors, storageType, result, ref data);
|
|
|
|
// NOTE: It is critically important that we pass the actual objectType down instead of
|
|
// using result.GetType() because it is not guaranteed that result.GetType()
|
|
// will equal objectType, especially because some converters are known to
|
|
// return dummy values for CreateInstance() (for example, the default behavior
|
|
// for structs is to just return the type of the struct).
|
|
|
|
return deserializeResult += InternalDeserialize_4_Cycles(overrideConverterType, data, objectType, ref result);
|
|
}
|
|
|
|
private fsResult InternalDeserialize_4_Cycles(Type overrideConverterType, fsData data, Type resultType, ref object result) {
|
|
if (IsObjectDefinition(data)) {
|
|
// NOTE: object references are handled at stage 1
|
|
|
|
// If this is a definition, then we have a serialization invariant that this is the
|
|
// first time we have encountered the object (TODO: verify in the deserialization logic)
|
|
|
|
// Since at this stage in the deserialization process we already have access to the
|
|
// object instance, so we just need to sync the object id to the references database
|
|
// so that when we encounter the instance we lookup this same object. We want to do
|
|
// this before actually deserializing the object because when deserializing the object
|
|
// there may be references to itself.
|
|
|
|
int sourceId = int.Parse(data.AsDictionary[Key_ObjectDefinition].AsString);
|
|
_references.AddReferenceWithId(sourceId, result);
|
|
}
|
|
|
|
// Nothing special, go through the standard deserialization logic.
|
|
return InternalDeserialize_5_Converter(overrideConverterType, data, resultType, ref result);
|
|
}
|
|
|
|
private fsResult InternalDeserialize_5_Converter(Type overrideConverterType, fsData data, Type resultType, ref object result) {
|
|
if (IsWrappedData(data)) {
|
|
data = data.AsDictionary[Key_Content];
|
|
}
|
|
|
|
return GetConverter(resultType, overrideConverterType).TryDeserialize(data, ref result, resultType);
|
|
}
|
|
}
|
|
}
|