using Jtr.Parsing.Json; using Jtr.Parsing.Json.Syntax; using Jtr.Tools.Extensions; using Jtr.Parsing; using Jtr.Parsing.Extensions; namespace Jtr.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.Name.Push(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.Reverse(); context.Classes.ForEach(@class => @class.Translate(context)); context.Name.Pop(); return context.Builder.ToString(); } private static ITranslation Translate(IJsonSyntax visitee, Context context) { return Visitors[visitee.GetType()](visitee, context); } private static ITranslation Object(IJsonSyntax visitee, Context context) { var @object = visitee.As(); var @class = new ClassTranslation { Name = context.CurrentClassName, Properties = new() }; context.Class.Push(@class); context.Scope.Push(Context.TranslationScope.Object); foreach (var prop in @object.Properties) { context.Name.Push(prop.Key.Lexeme); var translation = Translate(prop.Value, context); switch (translation) { case ClassTranslation: // TODO: Handle class exists context.Class.Peek().Properties.Add(new PropertyTranslation { Type = translation.Name, Name = prop.Key.Lexeme, }); break; case PropertyTranslation property: // TODO: Handle property exists context.Class.Peek().Properties.Add(property); break; } context.Name.Pop(); } if (!context.Scope.IsChildOf(Context.TranslationScope.Array)) { context.Classes.Add(@class); } context.Scope.Pop(); return context.Class.Pop(); } private static ITranslation Array(IJsonSyntax visitee, Context context) { var array = visitee.As(); var type = "object"; context.Scope.Push(Context.TranslationScope.Array); if (array.Elements.Length == 0) { type = "object"; } else if (array.Elements.All(e => e is JsonObjectSyntax)) { type = context.CurrentClassName; var composite = array.Elements .Select(el => Translate(el, context)) .Cast(); var @class = new ClassTranslation { Name = type, Properties = composite .SelectMany(cls => cls.Properties) .ToList(), }; context.Classes.Add(@class); } 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"; } context.Scope.Pop(); return new PropertyTranslation { Type = $"List<{type}>", Name = context.Name.Peek(), }; } 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.Name.Peek(), }; } private static ITranslation Number(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = visitee.As().Token.Lexeme.Contains('.') ? "double" : "int", Name = context.Name.Peek(), }; } private static ITranslation Bool(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = "bool", Name = context.Name.Peek(), }; } private static ITranslation Null(IJsonSyntax visitee, Context context) { return new PropertyTranslation { Type = "object", Name = context.Name.Peek(), }; } }