Encapsulating JQ JSON in C#

2023年6月6日 174点热度 1人点赞 2条评论
内容目录

Jq is a lightweight and flexible command-line JSON processor.
Official website:
https://jqlang.github.io/jq/
https://jqlang.github.io/jq/manual/

Jq can parse data from JSON and replace data in field expressions to generate new JSON.

For example, a JSON:

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

Using the jq expression .foo? to extract data results in:

42

Extracting an array from JSON:

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

Using the expression .[0], the extraction result is:

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

Jq contains a large number of expressions; please refer to the official documentation for details.

Since Jq is written in C, and does not provide other ways of API, it needs to be encapsulated by oneself.

Download the corresponding platform version of the binary file from the official reference:

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

Then use C# to encapsulate the interface:

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(), "yworkflow");
        if (!Directory.Exists(TempPath))
        {
            Directory.CreateDirectory(TempPath);
        }
    }

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

    /// <summary>
    /// Execute processing rules on JSON
    /// </summary>
    /// <param name="json">The JSON to process</param>
    /// <param name="rule">JQ rule</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;
    }
}

Example 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; }    
        }
    }

Using the expression to extract JSON objects of people whose age is greater than or equal to 30:

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

After extracting into text, it can be printed or deserialized for testing:

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

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

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

痴者工良

高级程序员劝退师

文章评论