diff --git a/JsonButler/JsonButler.Tests/Generation/GenerationTests.cs b/JsonButler/JsonButler.Tests/Generation/GenerationTests.cs new file mode 100644 index 0000000..3ac9eb5 --- /dev/null +++ b/JsonButler/JsonButler.Tests/Generation/GenerationTests.cs @@ -0,0 +1,27 @@ +using System; +using Andeart.JsonButler.CodeGeneration; +using Andeart.JsonButler.Tests.Properties; +using Andeart.JsonButler.Tests.Utilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; + + + +namespace Andeart.JsonButler.Tests.Generation +{ + + [TestClass] + public class GenerationTests + { + [TestMethod] + public void GenerateCodeFile_ComplexData_CodeGenerated () + { + string input = Resources.ButlerJson0; + string generatedCsCode = ButlerCodeGenerator.GenerateCodeFile (input); + string expected = Resources.ButlerCs0; + + Tuple diff = TestUtilities.PeekAtFirstDiff (expected, generatedCsCode); + Assert.AreEqual (expected, generatedCsCode, $"\n{diff.Item1}\n{diff.Item2}"); + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/JsonButler.Tests.csproj b/JsonButler/JsonButler.Tests/JsonButler.Tests.csproj index b2e9790..3ec52ce 100644 --- a/JsonButler/JsonButler.Tests/JsonButler.Tests.csproj +++ b/JsonButler/JsonButler.Tests/JsonButler.Tests.csproj @@ -44,15 +44,39 @@ ..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll + + ..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + - + + + True + True + Resources.resx + + + + + + + + + {322ECCB4-5E68-4051-AE27-43E962EA014F} + JsonButler + + + + + ResXFileCodeGenerator + Resources.Designer.cs + diff --git a/JsonButler/JsonButler.Tests/JsonButlerTests.cs b/JsonButler/JsonButler.Tests/JsonButlerTests.cs deleted file mode 100644 index e6b4d11..0000000 --- a/JsonButler/JsonButler.Tests/JsonButlerTests.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; - - - -namespace JsonButler.Tests -{ - - [TestClass] - public class JsonButlerTests - { - [TestMethod] - public void SerializeType_CustomType_Serialized () { } - } - -} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/Properties/Resources.Designer.cs b/JsonButler/JsonButler.Tests/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a889441 --- /dev/null +++ b/JsonButler/JsonButler.Tests/Properties/Resources.Designer.cs @@ -0,0 +1,109 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Andeart.JsonButler.Tests.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Andeart.JsonButler.Tests.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to { + /// "name": "Rick Astley", + /// "lines": [ + /// "Never gonna give you up", + /// "Never gonna let you down", + /// "Never gonna run around", + /// "And desert you" + /// ], + /// "winning_number": 42, + /// "nested_type": { + /// "super_nested_type": { + /// "id": 3.14 + /// } + /// } + ///}. + /// + internal static string ButlerCs0 { + get { + return ResourceManager.GetString("ButlerCs0", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to { + /// "name": "Rick Astley", + /// "lines": [ + /// "Never gonna give you up", + /// "Never gonna let you down", + /// "Never gonna run around", + /// "And desert you" + /// ], + /// "winning_number": 42, + /// "nested_type": { + /// "super_nested_type": { + /// "id": 3.14 + /// } + /// } + ///}. + /// + internal static string ButlerJson0 { + get { + return ResourceManager.GetString("ButlerJson0", resourceCulture); + } + } + } +} diff --git a/JsonButler/JsonButler.Tests/Properties/Resources.resx b/JsonButler/JsonButler.Tests/Properties/Resources.resx new file mode 100644 index 0000000..bdf4c34 --- /dev/null +++ b/JsonButler/JsonButler.Tests/Properties/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Resources\ButlerCs0.cs;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + + ..\Resources\ButlerJson0.json;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/Resources/ButlerCs0.cs b/JsonButler/JsonButler.Tests/Resources/ButlerCs0.cs new file mode 100644 index 0000000..67904e4 --- /dev/null +++ b/JsonButler/JsonButler.Tests/Resources/ButlerCs0.cs @@ -0,0 +1,77 @@ +using System; +using Newtonsoft.Json; + +namespace JsonButler.Creations +{ + public class ButlerFoo + { + [JsonProperty("name")] + public string Name + { + get; + private set; + } + + [JsonProperty("lines")] + public string[] Lines + { + get; + private set; + } + + [JsonProperty("winning_number")] + public int WinningNumber + { + get; + private set; + } + + [JsonProperty("new_type")] + public NewType NewType + { + get; + private set; + } + + [JsonConstructor] + ButlerFoo(string name, string[] lines, int winningNumber, NewType newType) + { + Name = name; + Lines = lines; + WinningNumber = winningNumber; + NewType = newType; + } + } + + public class NewType + { + [JsonProperty("nested_type")] + public NestedType NestedType + { + get; + private set; + } + + [JsonConstructor] + NewType(NestedType nestedType) + { + NestedType = nestedType; + } + } + + public class NestedType + { + [JsonProperty("id")] + public float Id + { + get; + private set; + } + + [JsonConstructor] + NestedType(float id) + { + Id = id; + } + } +} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/Resources/ButlerJson0.json b/JsonButler/JsonButler.Tests/Resources/ButlerJson0.json new file mode 100644 index 0000000..49edf5a --- /dev/null +++ b/JsonButler/JsonButler.Tests/Resources/ButlerJson0.json @@ -0,0 +1,15 @@ +{ + "name": "Rick Astley", + "lines": [ + "Never gonna give you up", + "Never gonna let you down", + "Never gonna run around", + "And desert you" + ], + "winning_number": 42, + "new_type": { + "nested_type": { + "id": 3.14 + } + } +} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/Serialization/SerializationTests.cs b/JsonButler/JsonButler.Tests/Serialization/SerializationTests.cs new file mode 100644 index 0000000..a25a50d --- /dev/null +++ b/JsonButler/JsonButler.Tests/Serialization/SerializationTests.cs @@ -0,0 +1,190 @@ +using System.Reflection; +using Andeart.JsonButler.CodeSerialization; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + + + +namespace JsonButler.Tests.Serialization +{ + + [TestClass] + public class SerializationTests + { + [TestMethod] + public void SerializeType_SimpleCustomType_Serialized () + { + ButlerSerializerSettings serializerSettings = new ButlerSerializerSettings (Assembly.GetExecutingAssembly ()); + serializerSettings.PreferredAttributeTypesOnConstructor = new[] { typeof(JsonConstructorAttribute) }; + + JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings (); + jsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + jsonSerializerSettings.Formatting = Formatting.None; + serializerSettings.JsonSerializerSettings = jsonSerializerSettings; + + string serialized = ButlerSerializer.SerializeType (serializerSettings); + const string expected = ButlerTestClass0.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_NoButlerSerializerSettings_Serialized () + { + string serialized = ButlerSerializer.SerializeType (); + const string expected = ButlerTestClass0.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_NoPreferredCtorAttributes_Serialized () + { + ButlerSerializerSettings serializerSettings = new ButlerSerializerSettings (Assembly.GetExecutingAssembly ()); + + JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings (); + jsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + jsonSerializerSettings.Formatting = Formatting.None; + serializerSettings.JsonSerializerSettings = jsonSerializerSettings; + + string serialized = ButlerSerializer.SerializeType (serializerSettings); + const string expected = ButlerTestClass0.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_NoJsonSerializerSettings_Serialized () + { + ButlerSerializerSettings serializerSettings = new ButlerSerializerSettings (Assembly.GetExecutingAssembly ()); + serializerSettings.PreferredAttributeTypesOnConstructor = new[] { typeof(JsonConstructorAttribute) }; + + string serialized = ButlerSerializer.SerializeType (serializerSettings); + const string expected = ButlerTestClass0.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_JsonIgnoredProperty_PropertyIgnored () + { + string serialized = ButlerSerializer.SerializeType (); + const string expected = ButlerTestClass1.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_JsonConstructorAttribute_ConstructorRespected () + { + string serialized = ButlerSerializer.SerializeType (); + const string expected = ButlerTestClass2.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_ArrayProperty_SerializedAsArray () + { + string serialized = ButlerSerializer.SerializeType (); + const string expected = ButlerTestClass3.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + [TestMethod] + public void SerializeType_NoJsonPropertyAttribute_PropertySerialized () + { + string serialized = ButlerSerializer.SerializeType (); + const string expected = ButlerTestClass4.ExpectedSerialized; + string errorMessage = $"Expected: {expected}; Actual: {serialized}"; + Assert.AreEqual (expected, serialized, errorMessage); + } + + + private class ButlerTestClass0 + { + public const string ExpectedSerialized = "{\"never\":null}"; + + [JsonProperty ("never")] + public string Never { get; private set; } + + [JsonConstructor] + public ButlerTestClass0 (string never) + { + Never = never; + } + } + + + public class ButlerTestClass1 + { + public const string ExpectedSerialized = "{\"gonna\":0}"; + + [JsonProperty ("gonna")] + public int Gonna { get; private set; } + + [JsonIgnore] + public int Give { get; private set; } + + [JsonConstructor] + public ButlerTestClass1 (int gonna) + { + Gonna = gonna; + } + } + + + public class ButlerTestClass2 + { + public const string ExpectedSerialized = "{\"you\":42}"; + + [JsonProperty ("you")] + public int You { get; private set; } + + public ButlerTestClass2 (float you) + { + You = 7; + } + + [JsonConstructor] + public ButlerTestClass2 (string you) + { + You = you?.Length ?? 42; + } + } + + + public class ButlerTestClass3 + { + public const string ExpectedSerialized = "{\"up\":[],\"never\":[]}"; + + [JsonProperty ("up")] + public int[] Up { get; private set; } + + [JsonProperty ("never")] + public string[] Never { get; private set; } + + public ButlerTestClass3 (int[] up, string[] never) + { + Up = up; + Never = never; + } + } + + + public class ButlerTestClass4 + { + public const string ExpectedSerialized = "{\"Gonna\":false}"; + + public bool Gonna { get; } + + [JsonConstructor] + public ButlerTestClass4 (bool gonna) + { + Gonna = gonna; + } + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/Utilities/TestUtilities.cs b/JsonButler/JsonButler.Tests/Utilities/TestUtilities.cs new file mode 100644 index 0000000..f9acbf1 --- /dev/null +++ b/JsonButler/JsonButler.Tests/Utilities/TestUtilities.cs @@ -0,0 +1,87 @@ +using System; + + + +namespace Andeart.JsonButler.Tests.Utilities +{ + + internal static class TestUtilities + { + public static Tuple PeekAtFirstDiff (string source, string modified) + { + if (string.IsNullOrEmpty (source)) + { + if (string.IsNullOrEmpty (modified)) + { + return new Tuple ("No difference.", null); + } + + return new Tuple ("Source is blank. Modified contains:", modified); + } + + if (string.IsNullOrEmpty (modified)) + { + return new Tuple ("Modified is blank. Source contains:", source); + } + + // If any character is different from source, return peek at that position. + for (int i = 0; i < source.Length; i++) + { + if (source[i] != modified[i]) + { + return new Tuple ("Diff in source at:", PeekSnippetAtPosition (source, i)); + } + } + + // ...at this point, it must mean modified has all of source in it, at the correct positions. + // Check lengths. + + if (source.Length != modified.Length) + { + return new Tuple ("Diff in modified at:", PeekSnippetAtPosition (modified, source.Length)); + } + + return new Tuple ("No difference.", null); + } + + private static string PeekSnippetAtPosition (string source, int position) + { + int start = position; + int end = position; + int maxEndIndex = source.Length - 1; + + // Move start caret to nearest preceding word. If already in word, do nothing. + while (char.IsWhiteSpace (source[start]) && start > 0) + { + start--; + } + + // Continue moving start caret to start of this word. + while (!char.IsWhiteSpace (source[start]) && start > 0) + { + start--; + } + + // Move end caret to nearest following whitespace. If already in whitespace, do nothing. + while (!char.IsWhiteSpace (source[end]) && end < maxEndIndex) + { + end++; + } + + // Continue moving end caret to nearest following word. If already in word, do nothing. + while (char.IsWhiteSpace (source[end]) && end < maxEndIndex) + { + end++; + } + + // Continue moving end caret to start of this word. + while (!char.IsWhiteSpace (source[end]) && end < maxEndIndex) + { + end++; + } + + return source.Substring (start, end - start); + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler.Tests/packages.config b/JsonButler/JsonButler.Tests/packages.config index 88212eb..f77a41a 100644 --- a/JsonButler/JsonButler.Tests/packages.config +++ b/JsonButler/JsonButler.Tests/packages.config @@ -2,4 +2,5 @@ + \ No newline at end of file diff --git a/JsonButler/JsonButler.sln b/JsonButler/JsonButler.sln index c7e7bee..4d72c7e 100644 --- a/JsonButler/JsonButler.sln +++ b/JsonButler/JsonButler.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.27703.2042 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonButler", "JsonButler\JsonButler.csproj", "{322ECCB4-5E68-4051-AE27-43E962EA014F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonButler.Tests", "JsonButler.Tests\JsonButler.Tests.csproj", "{C4A42D52-7CEC-41BC-BADD-3B71C49BABAB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {322ECCB4-5E68-4051-AE27-43E962EA014F}.Debug|Any CPU.Build.0 = Debug|Any CPU {322ECCB4-5E68-4051-AE27-43E962EA014F}.Release|Any CPU.ActiveCfg = Release|Any CPU {322ECCB4-5E68-4051-AE27-43E962EA014F}.Release|Any CPU.Build.0 = Release|Any CPU + {C4A42D52-7CEC-41BC-BADD-3B71C49BABAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C4A42D52-7CEC-41BC-BADD-3B71C49BABAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C4A42D52-7CEC-41BC-BADD-3B71C49BABAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C4A42D52-7CEC-41BC-BADD-3B71C49BABAB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/JsonButler/JsonButler/CodeGeneration/ButlerCsFile.cs b/JsonButler/JsonButler/CodeGeneration/ButlerCodeGenerator.cs similarity index 88% rename from JsonButler/JsonButler/CodeGeneration/ButlerCsFile.cs rename to JsonButler/JsonButler/CodeGeneration/ButlerCodeGenerator.cs index e2ec712..1577163 100644 --- a/JsonButler/JsonButler/CodeGeneration/ButlerCsFile.cs +++ b/JsonButler/JsonButler/CodeGeneration/ButlerCodeGenerator.cs @@ -1,5 +1,4 @@ -using System.Windows.Forms; -using Andeart.JsonButler.CodeGeneration.Classes; +using Andeart.JsonButler.CodeGeneration.Classes; using Andeart.JsonButler.CodeGeneration.Namespaces; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -11,9 +10,9 @@ namespace Andeart.JsonButler.CodeGeneration { - public class ButlerCsFile + public class ButlerCodeGenerator { - public string GenerateCodeFile (string jsonText) + public static string GenerateCodeFile (string jsonText) { CompilationUnitSyntax compileUnit = SyntaxFactory.CompilationUnit (); compileUnit = compileUnit.AddUsings (SyntaxFactory.UsingDirective (SyntaxFactory.ParseName ("System"))); diff --git a/JsonButler/JsonButler/CodeGeneration/Classes/ButlerClassFactory.cs b/JsonButler/JsonButler/CodeGeneration/Classes/ButlerClassFactory.cs index 121204c..ad46217 100644 --- a/JsonButler/JsonButler/CodeGeneration/Classes/ButlerClassFactory.cs +++ b/JsonButler/JsonButler/CodeGeneration/Classes/ButlerClassFactory.cs @@ -13,6 +13,7 @@ internal class ButlerClassFactory public static ButlerClass Create (string className, string path, JToken jToken) { ButlerClass bClass = new ButlerClass (className, path); + bClass.SetAccessibility (ButlerAccessibility.Public); foreach (JToken childToken in jToken.Children ()) { diff --git a/JsonButler/JsonButler/CodeGeneration/Properties/ButlerPropertyFactory.cs b/JsonButler/JsonButler/CodeGeneration/Properties/ButlerPropertyFactory.cs index 6ad53b5..b25f0e1 100644 --- a/JsonButler/JsonButler/CodeGeneration/Properties/ButlerPropertyFactory.cs +++ b/JsonButler/JsonButler/CodeGeneration/Properties/ButlerPropertyFactory.cs @@ -23,19 +23,21 @@ public static ButlerProperty Create (JToken jToken) // Create property from type. bool requiresNewClass = JsonUtilities.GetTypeFrom (jProperty.Value, out string typeName); + string propertyId = jProperty.Name; - string propertyName = jProperty.Path.ToPascalCase (); - ButlerProperty bProperty = new ButlerProperty (propertyName, propertyId, typeName); + string propertyName = propertyId.ToPascalCase (); // Create additional type if needed. List dependencies = new List (); if (requiresNewClass) { + typeName = propertyName; ButlerClass dependency = ButlerClassFactory.Create (typeName, jProperty.Path, jProperty.Value); dependencies.Add (dependency); dependencies.AddRange (dependency.Dependencies); } + ButlerProperty bProperty = new ButlerProperty (propertyName, propertyId, typeName); bProperty.AddDependencyRange (dependencies); return bProperty; diff --git a/JsonButler/JsonButler/CodeSerialization/ButlerActivator.cs b/JsonButler/JsonButler/CodeSerialization/ButlerActivator.cs new file mode 100644 index 0000000..1fafb74 --- /dev/null +++ b/JsonButler/JsonButler/CodeSerialization/ButlerActivator.cs @@ -0,0 +1,74 @@ +using System; +using System.Reflection; +using Andeart.JsonButler.Utilities; + + + +namespace Andeart.JsonButler.CodeSerialization +{ + + public class ButlerActivator + { + public static object CreateInstance () + { + return CreateInstance (typeof(T)); + } + + public static object CreateInstance (ButlerSerializerSettings settings) + { + return CreateInstance (typeof(T), settings); + } + + public static object CreateInstance (Type type) + { + return CreateInstance (type, new ButlerSerializerSettings (type.Assembly)); + } + + public static object CreateInstance (Type type, ButlerSerializerSettings settings) + { + // Bail/simplify external objects first. + if (type.Assembly != settings.RootCallingAssembly) + { + return CreateDefaultInstance (type); + } + + ConstructorInfo constructorInfo = ReflectionUtilities.GetPreferredConstructor (type, settings.PreferredAttributeTypesOnConstructor); + if (type.IsValueType && constructorInfo == null) + { + return Activator.CreateInstance (type); + } + + return CreateInstanceWithConstructor (type, settings, constructorInfo); + } + + private static object CreateInstanceWithConstructor (Type type, ButlerSerializerSettings settings, ConstructorInfo constructorInfo) + { + ParameterInfo[] parameterInfos = constructorInfo.GetParameters (); + if (parameterInfos.Length == 0) + { + return Activator.CreateInstance (type); + } + + object[] parameterObjects = new object[parameterInfos.Length]; + for (int i = 0; i < parameterInfos.Length; i++) + { + Type parameterType = parameterInfos[i].ParameterType; + parameterObjects[i] = CreateInstance (parameterType, settings); + } + + object instance = constructorInfo.Invoke (parameterObjects); + return instance; + } + + private static object CreateDefaultInstance (Type type) + { + if (type.IsValueType || type.IsPrimitive) + { + return Activator.CreateInstance (type); + } + + return type.IsArray ? Activator.CreateInstance (type, 0) : null; + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler/CodeSerialization/ButlerSerializer.cs b/JsonButler/JsonButler/CodeSerialization/ButlerSerializer.cs index c5c5005..e17e61b 100644 --- a/JsonButler/JsonButler/CodeSerialization/ButlerSerializer.cs +++ b/JsonButler/JsonButler/CodeSerialization/ButlerSerializer.cs @@ -1,4 +1,5 @@ using System; +using Newtonsoft.Json; @@ -7,27 +8,26 @@ namespace Andeart.JsonButler.CodeSerialization public class ButlerSerializer { - public static void SerializeType () + public static string SerializeType () { - SerializeType (new ButlerSerializerSettings (typeof(T).Assembly)); + return SerializeType (typeof(T)); } - public static void SerializeType (ButlerSerializerSettings settings) { } - - private static object CreatePrimitiveObject () + public static string SerializeType (ButlerSerializerSettings settings) { - Type type = typeof(T); - return CreatePrimitiveObject (type); + return SerializeType (typeof(T), settings); } - private static object CreatePrimitiveObject (Type type) + public static string SerializeType (Type type) { - if (type.IsValueType || type.IsPrimitive) - { - return Activator.CreateInstance (type); - } + return SerializeType (type, new ButlerSerializerSettings (type.Assembly)); + } - return type.IsArray ? new object[0] : null; + public static string SerializeType (Type type, ButlerSerializerSettings settings) + { + object instance = ButlerActivator.CreateInstance (type, settings); + string instanceSerialized = JsonConvert.SerializeObject (instance, settings.JsonSerializerSettings); + return instanceSerialized; } } diff --git a/JsonButler/JsonButler/CodeSerialization/ButlerSerializerSettings.cs b/JsonButler/JsonButler/CodeSerialization/ButlerSerializerSettings.cs index f38c124..9d31721 100644 --- a/JsonButler/JsonButler/CodeSerialization/ButlerSerializerSettings.cs +++ b/JsonButler/JsonButler/CodeSerialization/ButlerSerializerSettings.cs @@ -1,21 +1,31 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace Andeart.JsonButler.CodeSerialization -{ - public class ButlerSerializerSettings - { - public Type[] PreferredConstructorTypes { get; set; } - - public Assembly RootCallingAssembly { get; set; } - - public ButlerSerializerSettings (Assembly rootCallingAssembly) - { - RootCallingAssembly = rootCallingAssembly; - } - } -} +using System; +using System.Reflection; +using Newtonsoft.Json; + + + +namespace Andeart.JsonButler.CodeSerialization +{ + + public class ButlerSerializerSettings + { + private static readonly Type[] _defaultPreferredAttributeTypesOnConstructor = { typeof(JsonConstructorAttribute) }; + + public Assembly RootCallingAssembly { get; } + + public Type[] PreferredAttributeTypesOnConstructor { get; set; } + + public JsonSerializerSettings JsonSerializerSettings { get; set; } + + public ButlerSerializerSettings (Assembly rootCallingAssembly) + { + RootCallingAssembly = rootCallingAssembly; + PreferredAttributeTypesOnConstructor = _defaultPreferredAttributeTypesOnConstructor; + + JsonSerializerSettings = new JsonSerializerSettings (); + JsonSerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + JsonSerializerSettings.Formatting = Formatting.None; + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler/JsonButler.csproj b/JsonButler/JsonButler/JsonButler.csproj index afae27a..fb62ba9 100644 --- a/JsonButler/JsonButler/JsonButler.csproj +++ b/JsonButler/JsonButler/JsonButler.csproj @@ -56,7 +56,6 @@ ..\packages\System.Text.Encoding.CodePages.4.3.0\lib\net46\System.Text.Encoding.CodePages.dll - @@ -65,15 +64,18 @@ - + + + + diff --git a/JsonButler/JsonButler/Utilities/CollectionUtilities.cs b/JsonButler/JsonButler/Utilities/CollectionUtilities.cs new file mode 100644 index 0000000..215f16d --- /dev/null +++ b/JsonButler/JsonButler/Utilities/CollectionUtilities.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + + + +namespace Andeart.JsonButler.Utilities +{ + + // This class only exists because this guy isn't a big fan of LINQ's performance. + // He will buy you a beer if you have a while to chat about it. + + + internal static class CollectionUtilities + { + public static bool IsNullOrEmpty (this IList list) + { + return list == null || list.Count == 0; + } + + public static bool Any (this IList list, Func predicate, out T element) + { + for (int i = 0; i < list.Count; i++) + { + if (predicate (list[i])) + { + element = list[i]; + return true; + } + } + + element = default(T); + return false; + } + } + +} \ No newline at end of file diff --git a/JsonButler/JsonButler/Utilities/JsonUtilities.cs b/JsonButler/JsonButler/Utilities/JsonUtilities.cs index 1f07e61..9eddc9e 100644 --- a/JsonButler/JsonButler/Utilities/JsonUtilities.cs +++ b/JsonButler/JsonButler/Utilities/JsonUtilities.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Andeart.CaseConversion; using Newtonsoft.Json.Linq; @@ -42,7 +41,7 @@ public static bool GetTypeFrom (JToken jToken, out string type) if (tokenType == JTokenType.Object) { - type = jToken.Path.ToPascalCase (); + // Don't bother setting the type here. Type name generated is based on owner property. return true; } diff --git a/JsonButler/JsonButler/Utilities/ReflectionUtilities.cs b/JsonButler/JsonButler/Utilities/ReflectionUtilities.cs new file mode 100644 index 0000000..25e0e5e --- /dev/null +++ b/JsonButler/JsonButler/Utilities/ReflectionUtilities.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; + + + +namespace Andeart.JsonButler.Utilities +{ + + internal static class ReflectionUtilities + { + public static ConstructorInfo GetPreferredConstructor (Type type, Type[] preferredAttributeTypes) + { + return GetPreferredConstructor (type.GetConstructors (), preferredAttributeTypes); + } + + public static ConstructorInfo GetPreferredConstructor (ConstructorInfo[] constructorInfos, Type[] preferredAttributeTypes) + { + if (constructorInfos.IsNullOrEmpty ()) + { + return null; + } + + if (preferredAttributeTypes.IsNullOrEmpty ()) + { + return constructorInfos[0]; + } + + if (preferredAttributeTypes.Any (IsNotAnAttribute, out Type failureType)) + { + throw new Exception($"Provided preferred-attribute-type {failureType} is not an Attribute type."); + } + + for (int i = 0; i < preferredAttributeTypes.Length; i++) + { + for (int j = 0; j < constructorInfos.Length; j++) + { + if (constructorInfos[j].GetCustomAttribute (preferredAttributeTypes[i]) != null) + { + return constructorInfos[j]; + } + } + } + + return constructorInfos[0]; + } + + private static bool IsSameOrSubclassOf (this Type derivedType, Type baseType) + { + return derivedType.IsSubclassOf (baseType) || derivedType == baseType; + } + + private static bool IsNotAnAttribute (this Type type) + { + return !type.IsSameOrSubclassOf (typeof(Attribute)); + } + } + +} \ No newline at end of file