Skip to content

Commit

Permalink
Add better support for serializing/deserializing to intermediate valu…
Browse files Browse the repository at this point in the history
…es (#30)
  • Loading branch information
xoofx committed Mar 10, 2022
1 parent 9d99bb3 commit 9b0e19e
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 10 deletions.
23 changes: 23 additions & 0 deletions src/Tomlyn.Tests/ModelTests/ReflectionModelTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ public void TestSampleModel()
Assert.AreEqual("this is a string", model["global"]);
}

[Test]
public void TestUri()
{
var expected = new Uri("https://example.com");
var text = @$"service_uri = ""{expected}""";
var options = new TomlModelOptions
{
ConvertToModel = (value, type) => type == typeof(Uri) ? new Uri((string)value) : null,
ConvertToToml = (value) => value is Uri uri ? uri.ToString() : null
};
var success = Toml.TryToModel<TestConfigWithUri>(text, out var result, out var diagnostics, null, options);

Assert.True(success);
Assert.AreEqual(expected, result?.ServiceUri);

var tomlText = Toml.FromModel(result!, options);
Assert.AreEqual($"service_uri = \"{expected}\"", tomlText.Trim());
}

private class TestConfigWithUri
{
public Uri? ServiceUri { get; set; }
}

/// <summary>
/// Serialize back and forth all integer/float primitives.
Expand Down
20 changes: 14 additions & 6 deletions src/Tomlyn/Model/Accessors/DynamicModelReadContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Reflection;
using Tomlyn.Model.Accessors;
Expand All @@ -16,7 +17,7 @@ public DynamicModelReadContext(TomlModelOptions options)
GetPropertyName = options.GetPropertyName;
ConvertPropertyName = options.ConvertPropertyName;
CreateInstance = options.CreateInstance;
ConvertTo = options.ConvertTo;
ConvertToModel = options.ConvertToModel;
Diagnostics = new DiagnosticsBag();
_accessors = new Dictionary<Type, DynamicAccessor>();
}
Expand All @@ -27,7 +28,7 @@ public DynamicModelReadContext(TomlModelOptions options)

public Func<Type, ObjectKind, object> CreateInstance { get; set; }

public Func<object, Type, object?>? ConvertTo { get; set; }
public Func<object, Type, object?>? ConvertToModel { get; set; }

public DiagnosticsBag Diagnostics { get; }

Expand Down Expand Up @@ -157,13 +158,20 @@ public bool TryConvertValue(SourceSpan span, object? value, Type changeType, out
}
}

outputValue = Convert.ChangeType(value, changeType);
return true;
try
{
outputValue = Convert.ChangeType(value, changeType);
return true;
}
catch (Exception) when(ConvertToModel is not null)
{
// ignore
}
}

if (ConvertTo is not null)
if (ConvertToModel is not null)
{
var convertedValue = ConvertTo(value, changeType);
var convertedValue = ConvertToModel(value, changeType);
outputValue = convertedValue;
if (convertedValue is not null)
{
Expand Down
3 changes: 3 additions & 0 deletions src/Tomlyn/Model/Accessors/DynamicModelWriteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ internal class DynamicModelWriteContext : DynamicModelReadContext
public DynamicModelWriteContext(TomlModelOptions options, TextWriter writer) : base(options)
{
Writer = writer;
ConvertToToml = options.ConvertToToml;
}

public TextWriter Writer { get; }

public Func<object, object?>? ConvertToToml { get; set; }
}
32 changes: 29 additions & 3 deletions src/Tomlyn/Model/ModelToTomlTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class ModelToTomlTransform
private readonly TextWriter _writer;
private readonly List<ObjectPath> _paths;
private readonly List<ObjectPath> _currentPaths;
private readonly Stack<List<KeyValuePair<string, object?>>> _tempPropertiesStack;
private ITomlMetadataProvider? _metadataProvider;

public ModelToTomlTransform(object rootObject, DynamicModelWriteContext context)
Expand All @@ -29,6 +30,7 @@ public ModelToTomlTransform(object rootObject, DynamicModelWriteContext context)
_context = context;
_writer = context.Writer;
_paths = new List<ObjectPath>();
_tempPropertiesStack = new Stack<List<KeyValuePair<string, object?>>>();
_currentPaths = new List<ObjectPath>();
}

Expand Down Expand Up @@ -167,10 +169,34 @@ private bool VisitObject(ObjectDynamicAccessor accessor, object currentObject, b

var previousMetadata = _metadataProvider;
_metadataProvider = currentObject as ITomlMetadataProvider;
var properties = _tempPropertiesStack.Count > 0 ? _tempPropertiesStack.Pop() : new List<KeyValuePair<string, object?>>();
try
{

var properties = accessor.GetProperties(currentObject);

// Pre-convert values to TOML values
var convertToToml = _context.ConvertToToml;
if (convertToToml != null)
{
foreach (var property in accessor.GetProperties(currentObject))
{
// Allow to convert a value to a TOML simpler value before serializing.
var value = property.Value;
if (value is not null)
{
var result = convertToToml(value);
if (result != null)
{
value = result;
}
properties.Add(new KeyValuePair<string, object?>(property.Key, value));
}
}
}
else
{
properties.AddRange(accessor.GetProperties(currentObject));
}

// Probe inline for each key
// If we require a key to be inlined, inline the rest
Expand All @@ -181,8 +207,6 @@ private bool VisitObject(ObjectDynamicAccessor accessor, object currentObject, b
bool lastInline = false;
if (!inline)
{
properties = new List<KeyValuePair<string, object?>>(properties);

foreach (var prop in properties)
{
lastValue = prop.Value;
Expand Down Expand Up @@ -238,6 +262,8 @@ private bool VisitObject(ObjectDynamicAccessor accessor, object currentObject, b
finally
{
_metadataProvider = previousMetadata;
properties.Clear();
_tempPropertiesStack.Push(properties);
}

return hasElements;
Expand Down
40 changes: 39 additions & 1 deletion src/Tomlyn/TomlModelOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,47 @@ public TomlModelOptions()
/// </remarks>
public Func<string, string> ConvertPropertyName { get; set; }

/// <summary>
/// Gets or sets the function used when deserializing from TOML to create instance of objects. Default is set to <see cref="DefaultCreateInstance"/>.
/// The arguments of the function are:
/// - The type to create an instance for.
/// - The expected <see cref="ObjectKind"/> from a TOML perspective.
/// Returns an instance according to the type and the expected <see cref="ObjectKind"/>.
/// </summary>
public Func<Type, ObjectKind, object> CreateInstance { get; set; }

public Func<object, Type, object>? ConvertTo { get; set; }
/// <summary>
/// Gets or sets the convert function called when deserializing a value from TOML to a model (e.g string to Uri).
///
/// Must return null if cannot convert. The arguments of the function are:
/// - The input object value to convert.
/// - The target type to convert to.
///
/// Returns an instance of target type converted from the input value or null if conversion is not supported.
/// </summary>
[Obsolete($"Use {nameof(ConvertToModel)} instead")]
public Func<object, Type, object?>? ConvertTo
{
get => ConvertToModel;
set => ConvertToModel = value;
}

/// <summary>
/// Gets or sets the convert function called when deserializing a value from TOML to a model (e.g string to Uri).
///
/// Must return null if cannot convert. The arguments of the function are:
/// - The input object value to convert.
/// - The target type to convert to.
///
/// Returns an instance of target type converted from the input value or null if conversion is not supported.
/// </summary>
public Func<object, Type, object?>? ConvertToModel { get; set; }

/// <summary>
/// Gets or sets the convert function called when serializing a value from a model to a TOML representation.
/// This function allows to substitute a value to another type before converting (e.g Uri to string).
/// </summary>
public Func<object, object?>? ConvertToToml { get; set; }

/// <summary>
/// Gets the list of the attributes used to ignore a property.
Expand Down

0 comments on commit 9b0e19e

Please sign in to comment.