Introduction to JMESPath - JSON processor you should definitely know

Oleksii Nikiforov - Jan 9 '23 - - Dev Community

TL;DR

JMESPath is a powerful query language that enables processing of JSON payloads. It can be used in .NET, see JmesPath.Net.

Source code: https://github.com/NikiforovAll/jmespath-demo

Introduction

JSON processing is a common task in the day-to-day work of developers. We are used to working with JSON, but, occasionally, we need something more dynamic and efficient than System.Text.Json and Newtonsoft.Json. JMESPath is a powerful query language that allows you to perform Map/Reduce tasks in a declarative and intuitive manner.

JMESPath is simple to use, the query itself is just a plain string. The benefit of this approach is that you can follow the inversion of control principle and give your users the control of writing JMESPath queries.

💡 For example, the Azure CLI uses the –query parameter to execute a JMESPath query on the results of commands.

public sealed class JmesPath
{
    public string Transform(string json, string expression);
}

Enter fullscreen mode Exit fullscreen mode

Demo

Read a random example of JSON string from a file:

var source = new StreamReader("./example.json").ReadToEnd();

Enter fullscreen mode Exit fullscreen mode

The content of the file:

{
  "_id": "63ba60670fe420f2fb346866",
  "isActive": true,
  "balance": "$2,285.51",
  "age": 20,
  "eyeColor": "blue",
  "name": "Eva Sharpe",
  "email": "evasharpe@zaggles.com",
  "phone": "+1 (950) 479-2130",
  "registered": "2023-01-08T08:07:44.1787922+00:00",
  "latitude": 46.325291,
  "longitude": 5.211461,
  "friends": [
    {
      "id": 0,
      "name": "Nielsen Casey",
      "age": 19
    },
    {
      "id": 1,
      "name": "Carlene Long",
      "age": 38
    }
  ]
}

Enter fullscreen mode Exit fullscreen mode

The code below shows the processing of the example payload above. It demonstrates different concepts such as projections, filtering, aggregation, type transformation, etc. I think the syntax is quite intuitive and doesn’t need an explanation.

The processing is quite simple:

var expressions = new (string, string)[]
{
    ("scalar", "balance"),
    ("projection", "{email: email, name: name}"),
    ("functions", "to_string(latitude)"),
    ("arrays", "friends[*].name"),
    ("filtering", "friends[?age > `20`].name"),
    ("aggregation", "{sum: sum(friends[*].age), names: join(',', friends[*].name)}"),
    ("now. ISO 8601", "now()"),
    ("now. Universal sortable date/time pattern", "now('u')"),
    ("now. Long date pattern", "now('D')"),
    ("format", "date_format(registered, 'd')"),
};

foreach (var (exampleName, expression) in expressions)
{
    var result = parser.Transform(source, expression);
}

Enter fullscreen mode Exit fullscreen mode

Extensibility

The cool thing about JMESPath is it provides a way to add custom functions. See more details: https://github.com/jdevillard/JmesPath.Net/issues/81

For example, here is how we can write now() function that accepts .NET string format provider, see the Microsoft docs https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings.

public class NowFunction : JmesPathFunction
{
    private const string DefaultDateFormat = "o";

    public NowFunction() : base("now", minCount: 0, variadic: true) { }

    public override JToken Execute(params JmesPathFunctionArgument[] args)
    {
        var format = args is { Length: > 0 } x
            ? x[0].Token
            : DefaultDateFormat;

        return new JValue(DateTimeOffset.UtcNow.ToString(format.ToString()));
    }
}

Enter fullscreen mode Exit fullscreen mode

Summary

As you can see, JMESPath solves the issues of dynamic JSON processing based on user input quite nicely. It has an extensibility model that opens tons of possibilities.

References

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .