/// <summary> /// Adds an <see cref="object"/> and its dependencies to the <see cref="CurrentGraph"/>. /// </summary> /// <param name="value">The <see cref="object"/> to add.</param> /// <returns>The created <see cref="SerializeNode"/> representing <paramref name="value"/>.</returns> private SerializeNode GetNode(object value) { if (value != null && !TypeConfiguration.MatchAny().Match(value?.GetType())) { throw new TypeMatchException(TypeConfiguration.MatchAny(), value?.GetType()); } var existing = CurrentGraph.Nodes.FirstOrDefault(n => object.ReferenceEquals(n.Value, value)); if (existing != null) { //// Existing nodes should be reused. return(existing); } else { var node = new SerializeNode(currentId.ToString(), value); currentId++; //// Adds the newly-created node to the graph. CurrentGraph.AddNode(node); //// Check if dependencies need to be resolved or if the value is native/null. if (value != null && !TypeConfiguration.MatchNative().Match(value.GetType())) { //// Get all of the dependencies as connections (unique for each individual node, so can construct them here). var dependencies = Providers.GetDependencies(value) .Select(p => new SerializeDependency(p.Key, node, GetNode(p.Value))); foreach (var dep in dependencies) { //// Add new dependencies to the graph. CurrentGraph.AddConnection(dep); } } else { node.IsNative = true; } return(node); } }
/// <summary> /// Constructs new instances of the object tree starting at the given <see cref="SerializeNode"/>, passing constructor parameters greedily where possible. /// </summary> /// <param name="startNode">The <see cref="SerializeNode"/> to start construction at.</param> private void Construct(SerializeNode startNode) { if (!constructed.Contains(startNode)) { try { if (startNode.IsNative) { //// Test only against native trusted types. if (startNode.DesiredType != null && !TypeConfiguration.MatchNative().Match(startNode.DesiredType)) { throw new TypeMatchException(TypeConfiguration.MatchNative(), startNode.DesiredType?.GetType()); } if (startNode.Value is JToken token) { if (token.Type == JTokenType.Null) { startNode.Value = null; } else { startNode.Value = token.ToObject(startNode.DesiredType); } constructed.Add(startNode); } else { throw new SerializationException("Native object not encapsulated in expected JToken value."); } } else { //// Test only against non-native trusted types. if (!TypeConfiguration.MatchTrusted().Match(startNode.DesiredType)) { throw new TypeMatchException(TypeConfiguration.MatchTrusted(), startNode.DesiredType?.GetType()); } //// Get all dependencies. var dependencies = CurrentGraph.GetConnections(startNode); dependencyMap.Add(startNode, new List <SerializeDependency>(dependencies)); //// Construct an object with non-circular dependencies. object newValue = Constructors.Construct( startNode.DesiredType, dependencies .Where(d => !IsCircular(d)) .ToDictionary( d => d.DependencyId, d => { Construct(d.EndNode); Populate(d.EndNode); return(d.EndNode.Value); }), out var usedKeys); dependencyMap[startNode].RemoveRange( dependencyMap[startNode].ToArray() .Where(d => usedKeys.Contains(d.DependencyId))); startNode.Value = newValue; constructed.Add(startNode); foreach (var dep in dependencyMap[startNode]) { Construct(dep.EndNode); } } } catch (Exception ex) { throw new SerializationException($"Constructing {GetPath(startNode)} failed.", ex); } } }