jtr/DevDisciples.Json.Tools/Json2CSharpTranslator.cs

170 lines
5.3 KiB
C#

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<IJsonSyntax, Context, ITranslation> Visitors = new();
static Json2CSharpTranslator()
{
Visitors.Register<JsonObjectSyntax>(Object);
Visitors.Register<JsonArraySyntax>(Array);
Visitors.Register<JsonStringSyntax>(String);
Visitors.Register<JsonNumberSyntax>(Number);
Visitors.Register<JsonBoolSyntax>(Bool);
Visitors.Register<JsonNullSyntax>(Null);
}
public static string Translate(string input, Context? context = null)
{
if (JsonParser.Parse("<input>", 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<JsonObjectSyntax>();
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<JsonArraySyntax>();
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<JsonStringSyntax>();
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<JsonNumberSyntax>().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<IJsonSyntax> 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;
}
}