Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add better constructor support for deserialization #32

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Tomlet.Tests/ClassDeserializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public void SimpleRecordDeserializationWorks()
Assert.Equal(new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc), type.MyDateTime);
}

[Fact]
public void ClassWithParameterlessConstructorDeserializationWorks()
{
var type = TomletMain.To<ClassWithParameterlessConstructor>(TestResources.SimplePrimitiveDeserializationTestInput);

Assert.Equal("Hello, world!", type.MyString);
}

[Fact]
public void AnArrayOfEmptyStringsCanBeDeserialized()
{
Expand All @@ -75,5 +83,23 @@ public void AttemptingToDeserializeADocumentWithAnIncorrectlyTypedFieldThrows()
var msg = $"While deserializing an object of type {typeof(SimplePrimitiveTestClass).FullName}, found field MyFloat expecting a type of Double, but value in TOML was of type String";
Assert.Equal(msg, ex.Message);
}

[Fact]
public void ShouldOverrideDefaultConstructorsValues()
{
var options = new TomlSerializerOptions { OverrideConstructorValues = true };
var type = TomletMain.To<ClassWithValuesSetOnConstructor>(TestResources.SimplePrimitiveDeserializationTestInput, options);

Assert.Equal("Hello, world!", type.MyString);
}

[Fact]
public void ShouldNotOverrideDefaultConstructorsValues()
{
var options = new TomlSerializerOptions { OverrideConstructorValues = false };
var type = TomletMain.To<ClassWithValuesSetOnConstructor>(TestResources.SimplePrimitiveDeserializationTestInput, options);

Assert.Equal("Modified on constructor!", type.MyString);
}
}
}
11 changes: 11 additions & 0 deletions Tomlet.Tests/ClassWithValuesSetOnConstructor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Tomlet.Tests;

public class ClassWithValuesSetOnConstructor
{
public ClassWithValuesSetOnConstructor(string myString)
{
MyString = "Modified on constructor!";
}

public string MyString { get; set; }
}
8 changes: 8 additions & 0 deletions Tomlet.Tests/ExceptionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,14 @@ public void UnicodeControlCharsThrowAnException() =>
[Fact]
public void UnInstantiableObjectsThrow() =>
AssertThrows<TomlInstantiationException>(() => TomletMain.To<IConvertible>(""));

[Fact]
public void MultipleParameterizedConstructorsThrow() =>
AssertThrows<TomlInstantiationException>(() => TomletMain.To<ClassWithMultipleParameterizedConstructors>(""));

[Fact]
public void AbstractClassDeserializationThrows() =>
AssertThrows<TomlInstantiationException>(() => TomletMain.To<AbstractClass>(""));

[Fact]
public void MismatchingTypesInPrimitiveMappingThrows() =>
Expand Down
27 changes: 8 additions & 19 deletions Tomlet.Tests/ObjectToStringTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,23 @@ public void SerializingSimplePropertyClassAndDeserializingAgainGivesEquivalentOb
[Fact]
public void SerializingSimpleTestRecordToTomlStringWorks()
{
var testObject = new SimpleTestRecord
{
MyBool = true,
MyFloat = 420.69f,
MyString = "Hello, world!",
MyDateTime = new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc)
};

var testObject = new SimpleTestRecord("Hello, world!", 420.69f, true,
new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc));
var serializedForm = TomletMain.TomlStringFrom(testObject);

Assert.Equal("MyString = \"Hello, world!\"\nMyFloat = 420.69000244140625\nMyBool = true\nMyDateTime = 1970-01-01T07:00:00", serializedForm.Trim());
}

[Fact]
public void SerializingSimpleTestRecordAndDeserializingAgainGivesEquivalentObject()
{
var testObject = new SimpleTestRecord
{
MyBool = true,
MyFloat = 420.69f,
MyString = "Hello, world!",
MyDateTime = new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc)
};
var testObject = new SimpleTestRecord("Hello, world!", 420.69f, true,
new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc));

var serializedForm = TomletMain.TomlStringFrom(testObject);

var deserializedAgain = TomletMain.To<SimpleTestRecord>(serializedForm);

Assert.Equal(testObject, deserializedAgain);
}

Expand All @@ -120,7 +109,7 @@ public void SerializingAnEmptyObjectGivesAnEmptyString()
public void AttemptingToDirectlySerializeNullThrows()
{
//We need to use a type of T that actually has something to serialize
Assert.Throws<ArgumentNullException>(() => TomletMain.DocumentFrom(typeof(SimplePrimitiveTestClass), null!));
Assert.Throws<ArgumentNullException>(() => TomletMain.DocumentFrom(typeof(SimplePrimitiveTestClass), null!, null));
}
}
}
13 changes: 4 additions & 9 deletions Tomlet.Tests/ObjectToTomlDocTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,20 +66,15 @@ public void SimplePropertyClassToTomlDocWorks()
Assert.Equal("Hello, world!", tomlDoc.GetString("MyString"));
Assert.Equal("1970-01-01T07:00:00", tomlDoc.GetValue("MyDateTime").StringValue);
}

[Fact]
public void SimpleTestRecordToTomlDocWorks()
{
var testObject = new SimpleTestRecord
{
MyBool = true,
MyFloat = 420.69f,
MyString = "Hello, world!",
MyDateTime = new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc)
};
var testObject = new SimpleTestRecord("Hello, world!", 420.69f, true,
new DateTime(1970, 1, 1, 7, 0, 0, DateTimeKind.Utc));

var tomlDoc = TomletMain.DocumentFrom(testObject);

Assert.Equal(4, tomlDoc.Entries.Count);
Assert.True(tomlDoc.GetBoolean("MyBool"));
Assert.True(Math.Abs(tomlDoc.GetFloat("MyFloat") - 420.69) < 0.01);
Expand Down
6 changes: 6 additions & 0 deletions Tomlet.Tests/TestModelClasses/AbstractClass.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Tomlet.Tests.TestModelClasses;

public abstract class AbstractClass
{

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Tomlet.Tests.TestModelClasses;

public class ClassWithMultipleParameterizedConstructors
{
public ClassWithMultipleParameterizedConstructors(string myString)
{
MyString = myString;
}

public ClassWithMultipleParameterizedConstructors(string myString, int age)
{
MyString = myString;
}

public string MyString { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Tomlet.Tests.TestModelClasses;

public class ClassWithParameterlessConstructor : ClassWithMultipleParameterizedConstructors
{
public ClassWithParameterlessConstructor() : base(string.Empty, int.MinValue)
{

}
}
13 changes: 3 additions & 10 deletions Tomlet.Tests/TestModelClasses/SimpleTestRecord.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
using System;

namespace Tomlet.Tests.TestModelClasses
{
public record SimpleTestRecord
{
public string MyString { get; init; }
public float MyFloat { get; init; }
public bool MyBool { get; init; }
public DateTime MyDateTime { get; init; }
}
}
namespace Tomlet.Tests.TestModelClasses;

public record SimpleTestRecord(string MyString, float MyFloat, bool MyBool, DateTime MyDateTime);
14 changes: 3 additions & 11 deletions Tomlet/Exceptions/TomlInstantiationException.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
using System;

namespace Tomlet.Exceptions
namespace Tomlet.Exceptions
{
public class TomlInstantiationException : TomlException
{
private readonly Type _type;

public TomlInstantiationException(Type type)
{
_type = type;
}

public override string Message => $"Could not find a no-argument constructor for type {_type.FullName}";
public override string Message =>
"Deserialization of types without a parameterless constructor or a singular parameterized constructor is not supported.";
}
}
19 changes: 19 additions & 0 deletions Tomlet/Exceptions/TomlParameterTypeMismatchException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Reflection;

namespace Tomlet.Exceptions
{
public class TomlParameterTypeMismatchException : TomlTypeMismatchException
{
private readonly Type _typeBeingInstantiated;
private readonly ParameterInfo _paramBeingDeserialized;

public TomlParameterTypeMismatchException(Type typeBeingInstantiated, ParameterInfo paramBeingDeserialized, TomlTypeMismatchException cause) : base(cause.ExpectedType, cause.ActualType, paramBeingDeserialized.ParameterType)
{
_typeBeingInstantiated = typeBeingInstantiated;
_paramBeingDeserialized = paramBeingDeserialized;
}

public override string Message => $"While deserializing an object of type {_typeBeingInstantiated}, found parameter {_paramBeingDeserialized.Name} expecting a type of {ExpectedTypeName}, but value in TOML was of type {ActualTypeName}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
using System.Text;
using Tomlet.Exceptions;

namespace Tomlet
namespace Tomlet.Extensions
{
internal static class Extensions
internal static class GenericExtensions
{
private static readonly HashSet<int> IllegalChars = new()
{
Expand Down
36 changes: 36 additions & 0 deletions Tomlet/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Linq;
using System.Reflection;

namespace Tomlet.Extensions
{
internal static class ReflectionExtensions
{
internal static bool TryGetBestMatchConstructor(this Type type, out ConstructorInfo? bestMatchConstructor)
{
var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
if (constructors.Length == 0)
{
bestMatchConstructor = null;
return false;
}

var parameterlessConstructor = constructors.FirstOrDefault(c => c.GetParameters().Length == 0);
if (parameterlessConstructor != null)
{
bestMatchConstructor = parameterlessConstructor;
return true;
}

var parameterizedConstructors = constructors.Where(c => c.GetParameters().Length > 0).ToArray();
if (parameterizedConstructors.Length > 1)
{
bestMatchConstructor = null;
return false;
}

bestMatchConstructor = parameterizedConstructors.Single();
return true;
}
}
}
24 changes: 24 additions & 0 deletions Tomlet/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Text;

namespace Tomlet.Extensions
{
internal static class StringExtensions
{
internal static string ToPascalCase(this string str)
{
var sb = new StringBuilder(str.Length);

if (str.Length > 0)
{
sb.Append(char.ToUpper(str[0]));
}

for (var i = 1; i < str.Length; i++)
{
sb.Append(char.IsWhiteSpace(str[i - 1]) ? char.ToUpper(str[i]) : str[i]);
}

return sb.ToString();
}
}
}
1 change: 1 addition & 0 deletions Tomlet/Models/TomlString.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using Tomlet.Extensions;

namespace Tomlet.Models
{
Expand Down
3 changes: 2 additions & 1 deletion Tomlet/Models/TomlTable.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using Tomlet.Exceptions;
using Tomlet.Extensions;

namespace Tomlet.Models
{
Expand Down
Loading