diff --git a/YamlDotNet.Fsharp.Test/DeserializerTests.fs b/YamlDotNet.Fsharp.Test/DeserializerTests.fs index 3f48a4ff..b3ed65db 100644 --- a/YamlDotNet.Fsharp.Test/DeserializerTests.fs +++ b/YamlDotNet.Fsharp.Test/DeserializerTests.fs @@ -85,3 +85,84 @@ cars: person.Cars[1].Spec |> should be null person.Cars[1].Spec |> should equal None person.Cars[1].Nickname |> should equal None + + +[] +type TestSeq = { + name: string + numbers: int seq +} + +[] +let Deserialize_YamlSeq() = + let jackTheDriver = { + name = "Jack" + numbers = seq { 12; 2; 2 } + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build() + + let person = sut.Deserialize(yaml) + person.name |> should equal jackTheDriver.name + person.numbers |> should equalSeq jackTheDriver.numbers + +[] +type TestList = { + name: string + numbers: int list +} + +[] +let Deserialize_YamlList() = + let jackTheDriver = { + name = "Jack" + numbers = [ 12; 2; 2 ] + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build() + + let person = sut.Deserialize(yaml) + person |> should equal jackTheDriver + + +[] +type TestArray = { + name: string + numbers: int array +} + +[] +let Deserialize_YamlArray() = + let jackTheDriver = { + name = "Jack" + numbers = [| 12; 2; 2 |] + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build() + + let person = sut.Deserialize(yaml) + person |> should equal jackTheDriver diff --git a/YamlDotNet.Fsharp.Test/SerializerTests.fs b/YamlDotNet.Fsharp.Test/SerializerTests.fs index d612647c..e7ca88d1 100644 --- a/YamlDotNet.Fsharp.Test/SerializerTests.fs +++ b/YamlDotNet.Fsharp.Test/SerializerTests.fs @@ -146,7 +146,84 @@ cars: let person = sut.Serialize(jackTheDriver) person |> should equal yaml -type TestOmit = { +[] +type TestSeq = { + name: string + numbers: int seq +} + +[] +let Serialize_YamlSeq() = + let jackTheDriver = { + name = "Jack" + numbers = [ 12; 2; 2 ] + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) + .Build() + + let person = sut.Serialize(jackTheDriver) + person |> should equal yaml + + +[] +type TestList = { + name: string + numbers: int list +} + +[] +let Serialize_YamlList() = + let jackTheDriver = { + name = "Jack" + numbers = [ 12; 2; 2 ] + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) + .Build() + + let person = sut.Serialize(jackTheDriver) + person |> should equal yaml + +[] +type TestArray = { name: string - plop: int option + numbers: int array } + +[] +let Serialize_YamlArray() = + let jackTheDriver = { + name = "Jack" + numbers = [| 12; 2; 2 |] + } + + let yaml = """name: Jack +numbers: +- 12 +- 2 +- 2 +""" + let sut = SerializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull) + .Build() + + let person = sut.Serialize(jackTheDriver) + person |> should equal yaml diff --git a/YamlDotNet/Helpers/FsharpHelper.cs b/YamlDotNet/Helpers/FsharpHelper.cs index 304164b5..7d9a1998 100644 --- a/YamlDotNet/Helpers/FsharpHelper.cs +++ b/YamlDotNet/Helpers/FsharpHelper.cs @@ -5,14 +5,14 @@ namespace YamlDotNet.Helpers { public static class FsharpHelper { - private static bool IsFsharp(Type t) + private static bool IsFsharpCore(Type t) { return t.Namespace == "Microsoft.FSharp.Core"; } public static bool IsOptionType(Type t) { - return IsFsharp(t) && t.Name == "FSharpOption`1"; + return IsFsharpCore(t) && t.Name == "FSharpOption`1"; } public static Type? GetOptionUnderlyingType(Type t) @@ -34,5 +34,27 @@ public static bool IsOptionType(Type t) return objectDescriptor.Type.GetProperty("Value").GetValue(objectDescriptor.Value); } + + public static bool IsFsharpListType(Type t) + { + return t.Namespace == "Microsoft.FSharp.Collections" && t.Name == "FSharpList`1"; + } + + public static object? CreateFsharpListFromArray(Type t, Type itemsType, Array arr) + { + if (!IsFsharpListType(t)) + { + return null; + } + + var fsharpList = + t.Assembly + .GetType("Microsoft.FSharp.Collections.ListModule") + .GetMethod("OfArray") + .MakeGenericMethod(itemsType) + .Invoke(null, new[] { arr }); + + return fsharpList; + } } } diff --git a/YamlDotNet/Serialization/DeserializerBuilder.cs b/YamlDotNet/Serialization/DeserializerBuilder.cs index bf3ab577..38b4e9a2 100755 --- a/YamlDotNet/Serialization/DeserializerBuilder.cs +++ b/YamlDotNet/Serialization/DeserializerBuilder.cs @@ -104,7 +104,8 @@ public DeserializerBuilder() duplicateKeyChecking, typeConverter, enumNamingConvention) - } + }, + { typeof(FsharpListNodeDeserializer), _ => new FsharpListNodeDeserializer(enumNamingConvention) }, }; nodeTypeResolverFactories = new LazyComponentRegistrationList diff --git a/YamlDotNet/Serialization/NodeDeserializers/FsharpListNodeDeserializer.cs b/YamlDotNet/Serialization/NodeDeserializers/FsharpListNodeDeserializer.cs new file mode 100644 index 00000000..b2f0f613 --- /dev/null +++ b/YamlDotNet/Serialization/NodeDeserializers/FsharpListNodeDeserializer.cs @@ -0,0 +1,62 @@ +// This file is part of YamlDotNet - A .NET library for YAML. +// Copyright (c) Antoine Aubry and contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections; +using System.Collections.Generic; +using YamlDotNet.Core; +using YamlDotNet.Helpers; +using YamlDotNet.Serialization.Utilities; + +namespace YamlDotNet.Serialization.NodeDeserializers +{ + public sealed class FsharpListNodeDeserializer : INodeDeserializer + { + private readonly INamingConvention enumNamingConvention; + + public FsharpListNodeDeserializer(INamingConvention enumNamingConvention) + { + this.enumNamingConvention = enumNamingConvention; + } + + public bool Deserialize(IParser parser, Type expectedType, Func nestedObjectDeserializer, out object? value) + { + if (!FsharpHelper.IsFsharpListType(expectedType)) + { + value = false; + return false; + } + + var itemsType = expectedType.GetGenericArguments()[0]; + var collectionType = expectedType.GetGenericTypeDefinition().MakeGenericType(itemsType); + + var items = new ArrayList(); + CollectionNodeDeserializer.DeserializeHelper(itemsType, parser, nestedObjectDeserializer, items, true, enumNamingConvention); + + var array = Array.CreateInstance(itemsType, items.Count); + items.CopyTo(array, 0); + + var collection = FsharpHelper.CreateFsharpListFromArray(collectionType, itemsType, array); + value = collection; + return true; + } + } +}