using DevDisciples.Json.Parser; using DevDisciples.Json.Parser.Syntax; using DevDisciples.Parsing; using DevDisciples.Parsing.Extensions; using Humanizer; namespace DevDisciples.Json.Tools; public static partial class Json2CSharpTranslator { private static readonly VisitorContainer Visitors = new(); static Json2CSharpTranslator() { Visitors.Register(Object); Visitors.Register(Array); Visitors.Register(String); Visitors.Register(Number); Visitors.Register(Bool); Visitors.Register(Null); } public static string Translate(string input, Context? context = null) { if (JsonParser.Parse("", input) is not JsonObjectSyntax root) throw new SyntaxException("Expected a JSON object."); context ??= new(); context.CurrentName = context.RootClassName; var visitor = Visitors[typeof(JsonObjectSyntax)]; visitor(root, context); context.Builder.Append("//using System;\n"); context.Builder.Append("//using System.Collections.Generic;\n"); context.Builder.Append('\n'); context.Builder.Append($"namespace {context.Namespace};\n\n"); context.Classes.ForEach(@class => @class.Translate(context)); return context.Builder.ToString(); } private static ITranslation Object(IJsonSyntax visitee, Context context) { var @object = visitee.As(); var @class = new ClassTranslation { Name = context.CurrentName, Properties = new() }; context.Classes.Add(@class); foreach (var prop in @object.Properties) { context.CurrentName = prop.Key.Lexeme; var visitor = Visitors[prop.Value.GetType()]; var translation = visitor(prop.Value, context); switch (translation) { case ClassTranslation: @class.Properties.Add(new PropertyTranslation { Type = translation.Name, Name = translation.Name, }); break; case PropertyTranslation property: @class.Properties.Add(property); break; } } return @class; } private static ITranslation Array(IJsonSyntax visitee, Context context) { var array = visitee.As(); var type = "object"; if (array.Elements.Length == 0) { type = "object"; } else if (array.Elements.All(e => e is JsonObjectSyntax)) { context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context)); type = context.Classes.Last().Name; } else if (array.Elements.All(e => e is JsonStringSyntax es && DateTime.TryParse(es.Value, out _))) { type = "DateTime"; } else if (array.Elements.All(e => e is JsonStringSyntax)) { type = "string"; } else if (array.Elements.All(e => e is JsonNumberSyntax)) { type = array.Elements.Any(e => ((JsonNumberSyntax)e).Token.Lexeme.Contains('.')) ? "double" : "int"; } return new PropertyTranslation { Type = $"List<{type}>", Name = context.CurrentName, }; } private static ITranslation String(IJsonSyntax visitee, Context context) { var @string = visitee.As(); var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string"; return new PropertyTranslation { Type = type, Name = context.CurrentName, }; } private static ITranslation Number(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = visitee.As().Token.Lexeme.Contains('.') ? "double" : "int", Name = context.CurrentName, }; } private static ITranslation Bool(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = "bool", Name = context.CurrentName, }; } private static ITranslation Null(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = "object", Name = context.CurrentName, }; } private static ClassTranslation SquashObjects(string className, IEnumerable objects, Context context) { var classes = objects .Select(@object => Object(@object, context)) .ToArray(); var squashed = new ClassTranslation { Name = className.Singularize(), Properties = new() }; foreach (var @class in classes) foreach (var prop in ((ClassTranslation)@class).Properties) if (squashed.Properties.All(p => p.Name != prop.Name)) squashed.Properties.Add(prop); return squashed; } }