使用 C# 封装 JQ json

2023年6月6日 989点热度 1人点赞 2条评论
内容纲要

Jq 是一个轻量级和灵活的命令行 JSON 处理器。
官网:
https://jqlang.github.io/jq/
https://jqlang.github.io/jq/manual/

Jq 可以从 JSON 中解析数据以及将数据替换到字段表达式生成新的 Json。

例如一个 JSON:

    {"foo": 42, "bar": "less interesting data"}

使用 jq 表达式 .foo? 提取数据,结果:

42

提取数组的 JSON

    [
    {"name":"JSON", "good":true},
    {"name":"XML", "good":false}
    ]

使用表达式 .[0],提取结果:

{"name":"JSON", "good":true}

Jq 中包含了大量的表达式,详细参考官网文档。

由于 Jq 是 C 语言编写,并且没有提供其他方式的 API ,因此需要自己封装。

到官方参考下载对应平台版本的二进制文件:

├─linux
│      jq-linux64
│
└─windows
        jq-win64.exe

然后使用 C# 封装接口:

using System.Diagnostics;
using System.Runtime.InteropServices;

namespace JQ;

/// <summary>
/// json query
/// </summary>
public static partial class JQParse
{
    private static readonly string JqExePath;
    private static readonly string TempPath;
    static JQParse()
    {
        var path = Directory.GetParent(typeof(JQParse).Assembly.Location)?.FullName ??
                   throw new ArgumentNullException(
                       $"{typeof(JQParse).Assembly.GetName()} file path is null");
        JqExePath = RuntimeInformation.OSArchitecture switch
        {
            Architecture.X64 when OperatingSystem.IsWindows() => Path.Combine(path, "x64/windows/jq-win64.exe"),
            Architecture.X64 when OperatingSystem.IsLinux() => Path.Combine(path, "/x64/linux/jq-win64"),
            _ => throw new PlatformNotSupportedException(
                $"The current platform is not supported: {RuntimeInformation.OSArchitecture}")
        };

        TempPath = Path.Combine(Path.GetTempPath(), "sjzyworkflow");
        if (!Directory.Exists(TempPath))
        {
            Directory.CreateDirectory(TempPath);
        }
    }

    // <see href="https://stedolan.github.io/jq/manual/"/>

    /// <summary>
    /// 对 Json 执行处理规则
    /// </summary>
    /// <param name="json">要处理的 json</param>
    /// <param name="rule">JQ 规则</param>
    /// <returns></returns>
    public static async Task<string> Execute(string json,string rule)
    {
        string path = Path.Combine(TempPath,$"{Guid.NewGuid().ToString()}.json");
        await File.WriteAllTextAsync(path,json);
        StreamReader streamReader;

        string asString = string.Empty;
        string message = string.Empty;
        string arguments = $"-r \"{rule}\" {path}";

        using Process? process = Process.Start(new ProcessStartInfo(JqExePath, arguments)
        {
            CreateNoWindow = true,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            UseShellExecute = false
        });
        if (process == null)
        {
            throw new ArgumentNullException("Not found JQ process!");
        }

        await process.WaitForExitAsync(); 
        streamReader = process.StandardOutput;
        StreamReader standardError = process.StandardError;
        asString = await streamReader.ReadToEndAsync();
        message = await standardError.ReadToEndAsync();

        await process.WaitForExitAsync();
        process.Close();
        process.Dispose();

        streamReader.Close();
        streamReader.Dispose();

        if (message != string.Empty)
            throw new Exception(message);
        File.Delete(path);
        if (asString.EndsWith("\r\n")) return asString[0..(asString.Length - 2)];
        return asString;
    }
}

示例 JSON:

        const string json =
        """
        {
            "people": [
            {
                "fname": "Marry",
                "lname": "Allice",
                "address": "1234 SomeStreet",
                "age": 25
            },
            {
                "fname": "Kelly",
                "lname": "Mill",
                "address": "1234 SomeStreet",
                "age": 30
            }
            ]
        }
        """;

    public class Model
    {
        public P[] people { get; set; }

        public class P
        {
            public string fname { get; set; }
            public string lname { get; set; }
            public string address { get; set; }
            public int age { get; set; }    
        }
    }

使用表达式提取 JSON 中年龄大于等于 30 的人 json 对象:

        var rule = "{people: [.people[] | select(.age >= 30)]}";
        var text = JQParse.Execute(json, rule).Result;

提取成 text 后可以打印,或者反序列化测试:

       var obj1 = Newtonsoft.Json.Linq.JObject.Parse(text);

        var v1 = obj1.ToObject<Model>();

        Assert.Equal("Kelly", v1.people[0].fname);

痴者工良

高级程序员劝退师

文章评论

  • 码农很忙

    为什么不拿 C# 封装一个?

    2023年6月21日
    • 痴者工良

      @码农很忙 因为没人写得出。。。

      2023年7月9日