ykmnkmi / jinja.dart

Jinja2 template engine port for Dart.
https://pub.dev/packages/jinja
MIT License
52 stars 11 forks source link

JSON Path in Dart's Jinja2 object #29

Open tfabacher opened 2 months ago

tfabacher commented 2 months ago

One of the challenges with Jinja2 is that it does not support the JSON Path standard to query the JSON easily to get a value. It is easy to add to Python because it can simply be added, but there was no Dart conversion.

But now there is: https://github.com/f3ath/jessie https://pub.dev/packages/json_path

I think you will find Jinja MUCH MUCH easier and more powerful to use with JSON Path. We will try this with your great code and if you like the idea we can submit it to your main branch as a MR.

--Todd

f3ath commented 2 months ago

Yay go for it!

ykmnkmi commented 2 months ago

Interseting, you can implement it using Environment's getItem argument or writing filter:

import 'dart:convert';

import 'package:jinja/jinja.dart';
import 'package:json_path/json_path.dart';

void main() {
  var document = jsonDecode(_json);

  print('All prices in the store, by JSONPath:');

  JsonPath(r'$..price')
      .read(document)
      .map((match) => '${match.path}:\t${match.value}')
      .forEach(print);

  print('');

  Object? getItem(Object? item, Object? object) {
    if (object is Map<String, Object?> &&
        item is String &&
        item.startsWith(r'$')) {
      return JsonPath(item)
          .read(object)
          .map<List>((match) => [match.path, match.value]);
    }

    try {
      return (object as dynamic)[item];
    } on NoSuchMethodError {
      if (object == null) {
        rethrow;
      }

      return null;
    }
  }

  var filters = {
    'jsonPath': (Object? document, Object? path) {
      return getItem(path, document);
    }
  };

  var environment = Environment(getItem: getItem, filters: filters);

  print('All prices in the store, by getItem:');

  var template = environment.fromString(_template);
  print(template.render({'document': document}));

  print('All prices in the store, by filter:');

  var template2 = environment.fromString(_template2);
  print(template2.render({'document': document}));
}

const _json = '''
{
  "store": {
    "book": [
      {
        "category": "reference",
        "author": "Nigel Rees",
        "title": "Sayings of the Century",
        "price": 8.95
      },
      {
        "category": "fiction",
        "author": "Evelyn Waugh",
        "title": "Sword of Honour",
        "price": 12.99
      },
      {
        "category": "fiction",
        "author": "Herman Melville",
        "title": "Moby Dick",
        "isbn": "0-553-21311-3",
        "price": 8.99
      },
      {
        "category": "fiction",
        "author": "J. R. R. Tolkien",
        "title": "The Lord of the Rings",
        "isbn": "0-395-19395-8",
        "price": 22.99
      }
    ],
    "bicycle": {
      "color": "red",
      "price": 19.95
    }
  }
}
''';

const _template = r'''
{% for path, price in document['$..price'] %}
  {{- path }}: {{ price }}
{% endfor %}
''';

const _template2 = r'''
{% for path, price in document | jsonPath('$..price') %}
  {{- path }}: {{ price }}
{% endfor %}
''';

Output:

All prices in the store, by Jinja:
$['store']['book'][0]['price']: 8.95
$['store']['book'][1]['price']: 12.99
$['store']['book'][2]['price']: 8.99
$['store']['book'][3]['price']: 22.99
$['store']['bicycle']['price']: 19.95

All prices in the store, by filter:
$['store']['book'][0]['price']: 8.95
$['store']['book'][1]['price']: 12.99
$['store']['book'][2]['price']: 8.99
$['store']['book'][3]['price']: 22.99
$['store']['bicycle']['price']: 19.95