0

From this json:

{
  "obj": [
    {
      "int": 0
    },
    {
      "int": 1
    }
  ]
}

I would like to obtain the list of int property of the field obj so I can do an aggregation on the list, e.g. taking the mean of int in obj, using Jinja2 syntax.

I have tried using the filter selectattr("int") combined with the sum filter but I get `unsupported operand type(s) for +: 'int' and 'dict'.

I am using Jinja2 with docxtpl to generate docx file from template.

import json
j = '{"obj": [{"int": 0},{"int": 1}]}'
context = json.loads(j)

from docxtpl import DocxTemplate
import jinja2

tpl = DocXtemplate('template.docx')
tpl.render(context, jinja_env)
tpl.save('out.docx')

The docx template contains the single line:

{{obj|selectattr("int")|sum}}

I would like to see 1 in the doxc file when taking the sum as I tried to do just above, I would also like to be able to do the mean and eventually reuse it in other operations.

EDIT: As an answer to those saying I should do it inside python:

I am developing a tool for generating docx reports from a database and a template file. The first part of the tool is a C++ program that generates json data from the database. The second part is a script for generating the report in docx format from a template using Jinja2. I would prefer the user only to mess with the template and not having to modify the script, also I would like to have some modularity and be able to compute aggregations over the json tree. Since Jinja2 is providing sum on list and other sort of operations, I thought it could add another layer of modularity without me having to write more code. At the moment I already added the possibility for the user to do aggregationS on the json tree and insert it into the node tree, but my solution is not perfect and it comes at the cost of providing another template file for data generation and it makes the tool more complex.

I hope it makes more sense now why I don't want to do it in the script.

Edit2: A slightly more complex example:

{
    "arr": [
        {
            "obj": [
                {
                    "name": 0
                },
                {
                    "name": 1
                }
            ]
        },
        {
            "obj": [
                {
                    "name": 0
                },
                {
                    "name": 1
                }
            ]
        }
    ]
}

I would like to be able to do something like:

{{arr|get_attr_list('obj')|get_attr_list('int')}}

An even better solution would be able to do list extension in Jinja2 directly:

{{ [ y[int] for y in x[obj] for x in arr ] | sum }}
Nick Skywalker
  • 1,027
  • 2
  • 10
  • 26
  • See here https://stackoverflow.com/questions/41793024/finding-the-sum-of-numbers-in-jinja2-template-using-flask?rq=1 but you better do it with python – balderman Jul 09 '19 at 08:53
  • I am looking for something less cumbersome. See my answer to this post to have an idea. – Nick Skywalker Jul 09 '19 at 09:33

3 Answers3

3

This is absolutely not something you should do in Jinja. That's a template language, for displaying things.

You should do this in Python, where it's a simple one-liner:

sum(item["int"] for item in context["obj"])
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
0

Here

d= {
  "obj": [
    {
      "int": 0
    },
    {
      "int": 1
    }
  ]
}
# create a list
lst = [e['int'] for e in d['obj']]
print(lst)
# now you can do stats on this list
import statistics
mean = statistics.mean(lst)
print(mean)
balderman
  • 22,927
  • 7
  • 34
  • 52
0

Full answer working on both the easy and slightly more complex example:

I can provide a filter that returns a list, in order to compose it I should check whether the field of interest is a list, if so I should flatten the result. Border case needs to be more defined but it works:

def get_list(value, arg):
  if type(value) is dict:
    return value[arg]
  elif type(value) is list:
    if len(value) == 0:
        return []
    else:
      res = [ x[arg] for x in value ]
      if type(res[0]) is list:
        res = list(itertools.chain(*res))
      return res
  else:
    return value

Then this work as expected:

first example

{{ obj|get_list("int")|sum }}

(output 1)

second example

{{ arr|get_list("obj")|get_list("int")|sum }}

(output 2)

Nick Skywalker
  • 1,027
  • 2
  • 10
  • 26