前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >学习Source Generators之从swagger中生成类

学习Source Generators之从swagger中生成类

作者头像
饭勺oO
发布2024-04-03 08:47:43
1290
发布2024-04-03 08:47:43
举报
文章被收录于专栏:饭勺oO的技术博客

前面学习了一些Source Generators的基础只是,接下来就来实践一下,用这个来生成我们所需要的代码。 本文将通过读取swagger.json的内容,解析并生成对应的请求响应类的代码。

创建项目

首先还是先创建两个项目,一个控制台程序,一个类库。

添加swagger文件

在控制台程序中添加Files目录,并把swagger文件放进去。别忘了还需要添加AdditionalFiles。

代码语言:javascript
复制
<ItemGroup>
  <AdditionalFiles Include="Files\swagger.json" />
</ItemGroup>

实现ClassFromSwaggerGenerator

安装依赖

由于我们需要解析swagger,所以需要安装一下JSON相关的包。这里我们安装了Newtonsoft.Json。 需要注意的是,依赖第三方包的时候需要在项目文件添加下面内容:

代码语言:javascript
复制
<PropertyGroup>
  <GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths" AfterTargets="ResolvePackageDependenciesForBuild">
  <ItemGroup>
    <TargetPathWithTargetPlatformMoniker Include="@(ResolvedCompileFileDefinitions)" IncludeRuntimeDependency="false" />
  </ItemGroup>
</Target>

否则编译时会出现FileNotFound的异常。

构建管道

这里我们通过AdditionalTextsProvider筛选以及过滤我们的swagger文件。

代码语言:javascript
复制
var pipeline = context.AdditionalTextsProvider.Select(static (text, cancellationToken) =>
  {
      if (!text.Path.EndsWith("swagger.json", StringComparison.OrdinalIgnoreCase))
      {
          return default;
      }

      return JObject.Parse(text.GetText(cancellationToken)!.ToString());
  })
    .Where((pair) => pair is not null);

实现生成代码逻辑

接下来我们就解析Swagger中的内容,并且动态拼接代码内容。主要代码部分如下:

代码语言:javascript
复制
context.RegisterSourceOutput(pipeline, static (context, swagger) =>
 {

     List<(string name, string sourceString)> sources = new List<(string name, string sourceString)>();


     #region 生成实体
     var schemas = (JObject)swagger["components"]!["schemas"]!;
     foreach (JProperty item in schemas.Properties())
     {
         if (item != null)
         {
             sources.Add((HandleClassName(item.Name), $@"#nullable enable
using System;
using System.Collections.Generic;

namespace SwaggerEntities;
public {ClassOrEnum((JObject)item.Value)} {HandleClassName(item.Name)} 
{{
    {BuildProperty((JObject)item.Value)}
}}
"));
         }
     }
     foreach (var (name, sourceString) in sources)
     {
         var sourceText = SourceText.From(sourceString, Encoding.UTF8);

         context.AddSource($"{name}.g.cs", sourceText);
     }
     #endregion
     });

完整的代码如下:

代码语言:javascript
复制
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;

namespace GenerateClassFromSwagger.Analysis
{
    [Generator]
    public class ClassFromSwaggerGenerator : IIncrementalGenerator
    {
        public void Initialize(IncrementalGeneratorInitializationContext context)
        {
            var pipeline = context.AdditionalTextsProvider.Select(static (text, cancellationToken) =>
            {
                if (!text.Path.EndsWith("swagger.json", StringComparison.OrdinalIgnoreCase))
                {
                    return default;
                }

                return JObject.Parse(text.GetText(cancellationToken)!.ToString());
            })
            .Where((pair) => pair is not null);

            context.RegisterSourceOutput(pipeline, static (context, swagger) =>
            {

                List<(string name, string sourceString)> sources = new List<(string name, string sourceString)>();


                #region 生成实体
                var schemas = (JObject)swagger["components"]!["schemas"]!;
                foreach (JProperty item in schemas.Properties())
                {
                    if (item != null)
                    {
                        sources.Add((HandleClassName(item.Name), $@"#nullable enable
using System;
using System.Collections.Generic;

namespace SwaggerEntities;
public {ClassOrEnum((JObject)item.Value)} {HandleClassName(item.Name)} 
{{
    {BuildProperty((JObject)item.Value)}
}}
                "));
                    }
                }
                foreach (var (name, sourceString) in sources)
                {
                    var sourceText = SourceText.From(sourceString, Encoding.UTF8);

                    context.AddSource($"{name}.g.cs", sourceText);
                }
                #endregion
            });
        }

        static string HandleClassName(string name)
        {
            return name.Split('.').Last().Replace("<", "").Replace(">", "").Replace(",", "");
        }
        static string ClassOrEnum(JObject value)
        {
            return value.ContainsKey("enum") ? "enum" : "partial class";
        }


        static string BuildProperty(JObject value)
        {
            var sb = new StringBuilder();
            if (value.ContainsKey("properties"))
            {
                var propertys = (JObject)value["properties"]!;
                foreach (JProperty item in propertys!.Properties())
                {
                    sb.AppendLine($@"
    public {BuildProertyType((JObject)item.Value)} {ToUpperFirst(item.Name)}  {{ get; set; }}
");
                }
            }
            if (value.ContainsKey("enum"))
            {
                foreach (var item in JsonConvert.DeserializeObject<List<int>>(value["enum"]!.ToString())!)
                {
                    sb.Append($@"
    _{item},
");
                }
                sb.Remove(sb.Length - 1, 1);
            }
            return sb.ToString();
        }

        static string BuildProertyType(JObject value)
        {
            ;
            var type = GetType(value);
            var nullable = value.ContainsKey("nullable") ? value["nullable"]!.Value<bool?>() switch
            {
                true => "?",
                false => "",
                _ => ""
            } : "";
            return type + nullable;
        }

        static string GetType(JObject value)
        {
            return value.ContainsKey("type") ? value["type"]!.Value<string>() switch
            {
                "string" => "string",
                "boolean" => "bool",
                "number" => value["format"]!.Value<string>() == "float" ? "float" : "double",
                "integer" => value["format"]!.Value<string>() == "int32" ? "int" : "long",
                "array" => ((JObject)value["items"]!).ContainsKey("items") ?
                $"List<{HandleClassName(value["items"]!["$ref"]!.Value<string>()!)}>"
                : $"List<{GetType((JObject)value["items"]!)}>",
                "object" => value.ContainsKey("additionalProperties") ? $"Dictionary<string, {GetType((JObject)value["additionalProperties"]!)}>" : "object",
                _ => "object"
            } : value.ContainsKey("$ref") ? HandleClassName(value["$ref"]!.Value<string>()!) : "object";
        }

        static unsafe string ToUpperFirst(string str)
        {
            if (str == null) return null;
            string ret = string.Copy(str);
            fixed (char* ptr = ret)
                *ptr = char.ToUpper(*ptr);
            return ret;
        }
    }
}

详细的处理过程大家可以仔细看看代码,这里就不一一解释了。

启动编译

接下来编译控制台程序。编译成功后可以看到生成了很多cs的代码。若是看不见,可以重启VS。

点开一个文件,可以看到内容,并且在上方提示自动生成,无法编辑。

到这我们就完成了通过swagger来生成我们的请求和响应类的功能。

结语

本文章应用SourceGenerator,在编译时读取swagger.json的内容并解析,成功生成了我们API的请求和响应类的代码。 我们可以发现,代码生成没有问题,无法移动或者编辑生成的代码。 下一篇文章我们就来学习下如何输出SourceGenerator生成的代码文件到我们的文件目录。

本文代码仓库地址https://github.com/fanslead/Learn-SourceGenerator

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 创建项目
  • 添加swagger文件
  • 实现ClassFromSwaggerGenerator
    • 安装依赖
      • 构建管道
        • 实现生成代码逻辑
        • 启动编译
        • 结语
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档