Dot notation in Nunjucks sorting isn't working - nunjucks

I'm using Eleventy (11ty) with Nunjucks. I have some JSON data that I am trying to sort. The Jinja documentation says that you can sort by attribute using dot notation, but when I try sorting by address.city, nothing happens:
{% for item in testData|sort(attribute="address.city") %}
{{ item.name }}
{% endfor %}
It does work if I sort without dot notation/by a top level field (name):
{% for item in testData|sort(attribute="name") %}
{{ item.name }}
{% endfor %}
My test data (testData.json):
[
{
"name": "AAA",
"address":
{
"city": "A?"
},
"salary": 2,
"married": true
},
{
"name": "III",
"address": {
"city": "D?"
},
"salary": 1,
"married": true
}
]

So, as seen in the comments to my question, sorting by dot notation isn't supported in Nunjucks currently.
What I did in the end, to get what I needed in my Nunjucks template in Eleventy, was to create a custom filter, inside of .eleventy.js, along the lines of:
eleventyConfig.addFilter("sortByCity", arr => {
arr.sort((a, b) => (a.address.city) > (b.address.city) ? 1 : -1);
return arr;
});
Then, in my Nunjucks template:
{% for item in testData | sortByCity %}
{{ item.name }}
{% endfor %}
I know this answer is more 11ty-specific, but I think this could quite possibly work for other environments to extend Nunjucks. Hopefully this will be of help to someone else in the future. Nunjucks docs about Filters are here

Related

Easy problem: Unable to output the yml data with liquid

I have a problem very similar to this one, but unfortunately, the solution doesn't work for me. So I'm posting another question.
I'm making a website with Jekyll and would like to print the information of a yml file on a page or post. Here is a dummy yml file:
- Master student:
- Dummy name1:
- email: blabal#bla.com
- room: 1
- tel: 12345678
- Bachelor student:
- Dummy name:
- email: blabal#bla.com
- room: 12
- tel: 0987654
- Guest:
- Bird:
- email: blabal#bla.com
- room: 10856
- tel: 71864270
This file is placed in a newly made _data directory, under the name people.yml. Then, I made a new post on a freshly made site with the minimal theme, which goes as follows:
---
layout: post
title: "Test"
date: 2023-01-12 23:42:09 +0100
---
{% assign people = site.data.people | read_yaml %}
<!-- {{ people }} --> // this tests if the files is loaded or not
{% for role in people %}
{% assign role_data = role[1] %}
{{ role_data[0] }} has the following role: {{ role[0] }}.
{% endfor %}
Once I generate the site, I expect the following text on the post (except maybe for some linebreaks):
Dummy name1 has the following role: Master student
Dummy name has the following role: Bachelor student
Bird has the following role: Guest
Instead, I get this:
has the following role: .
has the following role: .
has the following role: .
that puzzles me. I suspect the reason is the way I access the item values. But can't figure it out. Removing the read_yml also seems to have no effect.
You were on the right track, but didn't do enough un-nesting of your hashes. The data structure is a bit unfortunate; maybe when you look at the equivalent JSON, it's more obvious:
[
{
"Master student": [
{
"Dummy name1": [
{
"email": "blabal#bla.com"
},
{
"room": 1
},
{
"tel": 12345678
}
]
}
]
},
{
"Bachelor student": [
{
"Dummy name": [
{
"email": "blabal#bla.com"
},
{
"room": 12
},
{
"tel": 987654
}
]
}
]
},
{
"Guest": [
{
"Bird": [
{
"email": "blabal#bla.com"
},
{
"room": 10856
},
{
"tel": 71864270
}
]
}
]
}
]
So, each array element is a hash (object), and to get key and value, you have to iterate over the hashes; the values themselves are again arrays of hashes. This snippet produces your desired output:
{% assign people = site.data.people %}
{% for person in people %}
{%- for person_hash in person %}
{%- assign role = person_hash[0] %}
{%- for role_hash in person_hash[1][0] %}
{{- role_hash[0] }} has the following role: {{ role }}.
{% endfor %}
{% endfor %}
{% endfor %}
I would recommend you re-structure your data, to something like this:
- role: Master student
name: Dummy name1
email: blabal#bla.com
room: 1
tel: 12345678
- role: Bachelor student
name: Dummy name
email: blabal#bla.com
room: 12
tel: 0987654
- role: Guest
name: Bird
email: blabal#bla.com
room: 10856
tel: 71864270
which corresponds to this JSON:
[
{
"role": "Master student",
"name": "Dummy name1",
"email": "blabal#bla.com",
"room": 1,
"tel": 12345678
},
{
"role": "Bachelor student",
"name": "Dummy name",
"email": "blabal#bla.com",
"room": 12,
"tel": 987654
},
{
"role": "Guest",
"name": "Bird",
"email": "blabal#bla.com",
"room": 10856,
"tel": 71864270
}
]
Then, iterating over it becomes as simple as this:
{% for person in site.data.people %}
{{ person.name }} has the following role: {{ person.role }}
{% endfor %}

How can I print list values from FOR loop on a single line?

I am trying to get a nested list of dictionaries values printed to a single line using Jinja. I'm able to get them across multiple lines but I'm not sure how to get all values on a single line.
This is my example data Structure, trimmed for briefness
"interfaces": [
{
"display": "Ge1/0/1",
"enabled": true,
"id": 325,
"ip_addresses": [],
"label": "",
"lag": null,
"last_updated": "2022-05-12T22:42:29.740411Z",
"link_peer": null,
"link_peer_type": null,
"mac_address": null,
"mark_connected": false,
"mgmt_only": false,
"mode": {
"label": "Tagged",
"value": "tagged"
},
"mtu": null,
"name": "Ge1/0/1",
"parent": null,
"rf_channel": null,
"rf_channel_frequency": null,
"rf_channel_width": null,
"rf_role": null,
"tagged_vlans": [
{
"display": "Data (10)",
"id": 1,
"name": "Data",
"url": "http://10.10.0.144:8000/api/ipam/vlans/1/",
"vid": 10
},
{
"display": "VoIP (11)",
"id": 3,
"name": "VoIP",
"url": "http://10.10.0.144:8000/api/ipam/vlans/3/",
"vid": 11
},
{
"display": "MGMT (20)",
"id": 4,
"name": "MGMT",
"url": "http://10.10.0.144:8000/api/ipam/vlans/4/",
"vid": 20
}
],
This is my current Jinja2 code:
{% for interface in interfaces %}
interface {{ interface.display }}
{% for vlan in interface['tagged_vlans'] %}
untagged {{ vlan.vid }}
{% endfor %}
{% endfor %}
Which gives as my current output
interface Ge1/0/1
untagged 10
untagged 11
untagged 20
This is my expected output
interface Ge1/0/1
untagged 10,11,20
You don't need to loop here. It's much easier to join the values. In a nutshell:
{% for interface in interfaces %}
interface {{ interface.display }}
untagged {{ interface.tagged_vlans | map(attribute='vid') | join(',') }}
{% endfor %}
Meanwhile, if you ever need to loop and remove some white spaces/new lines from the output, see jinja2 whitespace control

Get only key from nested object with Jinja filter

I am using a Jinja filter in ansible to extract the value I need in the right format to process it.
This is the data in JSON format (I have shortened the output, usually there are much more variables per item and not all item have an IPv4 variable et all):
"interfaces": {
"GigabitEthernet0": {
"arp_timeout": "00:20:00",
"arp_type": "arpa",
"auto_negotiate": true,
"bandwidth": 1000000
},
"GigabitEthernet0/0/0": {
"arp_timeout": "00:20:00",
"arp_type": "arpa",
"auto_negotiate": true,
"bandwidth": 10000
},
"GigabitEthernet0/0/0.3": {
"arp_timeout": "04:00:00",
"arp_type": "arpa",
"bandwidth": 10000,
"delay": 10,
"description": "Private1 MPLS",
"enabled": true,
"encapsulations": {
"encapsulation": "dot1q",
"first_dot1q": "3"
},
"ipv4": {
"10.10.84.2/30": {
"ip": "10.10.84.2",
"prefix_length": "30"
}
That simple Jinja filer I use then to extract the information I need like the interface name and the IPv4:
[
{% for interface in interfaces if interfaces[interface]['ipv4'] is defined %}
{
"name": "{{ interface }}",
{% if interfaces[interface]['ipv4'] is defined %}
"prefix": "{{ interfaces[interface]['ipv4'] }}",
{% endif %}
"hostname": "{{ hostname }}"
}{{ ", " if not loop.last else "" }}
{% endfor %}
]
My problem is now that the parse data looks like this:
{
"name": "GigabitEthernet0/0/0.3",
"prefix": "{'10.10.84.2/30': {'ip': '10.10.84.2', 'prefix_length': '30'}}",
"hostname": "Horst1"
},
But I wanted to have only the key from the nested dict like this:
{
"name": "GigabitEthernet0/0/0.3",
"prefix": "10.10.84.2/30",
"hostname": "Horst1"
},
Isn't there a simple method in Jinja to get just the key from the nested object?
Here is for a possibly simpler template, using the for key, value in dict.items() construct:
[
{% for name, interface in interfaces.items() if interface.ipv4 is defined %}
{
"name": "{{ name }}",
"prefix": "{{ (interface.ipv4.keys() | list).0 }}",
"hostname": "{{ hostname }}"
}{{ ", " if not loop.last }}
{% endfor %}
]
The keys() method is the one from Python, that return a view representing a list of the keys of that dictionary. Cast it back to a list and take the first element of it, an you should be good to go.
Another option would be to use dict2items, once again, take the first element of the generated list and get its key:
"prefix": "{{ (interface.ipv4 | dict2items).0.key }}",
Neither Jinja nor iteration is needed. The expression below
name_prefix: "{{ interfaces|
dict2items|
selectattr('value.ipv4', 'defined')|
json_query('[].{name: key,
prefix: value.ipv4.keys(#)|[0]}') }}"
creates the list of the dictionaries
name_prefix:
- name: GigabitEthernet0/0/0.3
prefix: 10.10.84.2/30
Notes
The attribute ipv4 might be defined in more interfaces, hence the result is a list. Take the first item if you want to.
You can combine the dictionaries if you want to. For example,
horst_default:
hostname: Horst1
domain: foo.bar
horst: "{{ name_prefix|map('combine', horst_default)|list }}"
gives
horst:
- domain: foo.bar
hostname: Horst1
name: GigabitEthernet0/0/0.3
prefix: 10.10.84.2/30
You can convert the dictionary to JSON if you want to. For example,
- debug:
msg: |
{{ {'horst': horst|first}|to_nice_json }}
gives
{
"horst": {
"domain": "foo.bar",
"hostname": "Horst1",
"name": "GigabitEthernet0/0/0.3",
"prefix": "10.10.84.2/30"
}
}

How to combine 2 list based on a common key value in Ansible Jinja2

I am trying to combine a particular value into a list based on a common key(organizationId) value from a JSON array. Here i am using a Jinja template to create a Json file that will be used for further processing. So here sample.json is the JSON file from where the test.j2(Jinja template) will be fetching the values to make a final JSON(mentioned below as Expected output)
Sample.json
[
{
"id": "111222333444627213",
"organizationId": "111222333444624074"
},
{
"id": "111222333444627214",
"organizationId": "111222333444624074"
},
{
"id": "111222333444627216",
"organizationId": "111222333444624074"
},
{
"id": "111222333444627217",
"organizationId": "12345678"
}
]
test.j2(Jinja)
[
{% for dict_item in sample.json %}
{
"orgid":"{{dict_item['organizationId']}}",
"objectIds":[
"{{ dict_item['id']}}"
]
}{% if not loop.last %},
{% endif %}
{% endfor %}
]
Expected Output
[
{
"orgid":"111222333444624074",
"objectIds":[
"111222333444627213",
"111222333444627214",
"111222333444627216",
]
},
{
"orgid":"12345678",
"objectIds":[
"111222333444627217"
]
}
]
Jinja2 is not needed. Instead, iterate the list created by the filter groupby. For example
- set_fact:
output: "{{ output|d([]) + [{'orgid': item.0,
'objectIds': item.1|
map(attribute='id')|
list}] }}"
loop: "{{ sample.json|groupby('organizationId') }}"
gives
output:
- objectIds:
- '111222333444627213'
- '111222333444627214'
- '111222333444627216'
orgid: '111222333444624074'
- objectIds:
- '111222333444627217'
orgid: '12345678'

How to add Image in section in Shopify?

I want to add Image in the home page and for that, I added this code in customimg.liquid under section
{
"type": "image_picker",
"id": "image_1",
"label": "Image"
}
template->index.liquid
{% section 'customimg' %}
{{ settings.image_1 | img_url: 'master' | img_tag }}
The image is not showing up on the home page.
Could you please help me where I am missing.
Thanks
Since you are calling the section like so {% section 'customimg' %} instead of using {{ content_for_index }} I assume that you like the section to be static.
First you need to read a lot about how to use sections in here: https://help.shopify.com/en/themes/development/sections
Issues with your code
The structure inside the section must be written this way:
{% schema %}
{
"name": "Image",
"settings": [
{
"type": "image_picker",
"id": "image_1",
"label": "Image"
}
]
}
{% endschema %}
This is deprecated img_url: 'master' don't use it. Use this instead img_url: '2048x' .
This is wrong {{ settings.image_1 .... You must call it like so {{ section.settings.image_1 ...
You can't call the image outside the section! You must call the image inside the section, since the section object is accessible only inside the section.
How should your code look:
sections/customimg.liquid
{{ section.settings.image_1 | img_url: '2048x' | img_tag }}
{% schema %}
{
"name": "Image",
"settings": [
{
"type": "image_picker",
"id": "image_1",
"label": "Image"
}
]
}
{% endschema %}
templates/index.html
{% section 'customimg' %}
As an alternative your code can be dynamic and look like this
sections/custommg.liquid
{{ section.settings.image_1 | img_url: '2048x' | img_tag }}
{% schema %}
{
"name": "Image",
"settings": [
{
"type": "image_picker",
"id": "image_1",
"label": "Image"
}
],
"presets": [
{
"name": "Image",
"category": "Content"
}
]
}
{% endschema %}
templates/index.liquid
{{ content_for_index }}
The dynamic way allows you to add the section multiply times from the admin panel instead of a single one.

Resources