Dasel: zpracování a modifikace souborů JSON, YAML, XML a TOML

11. 4. 2023
Doba čtení: 29 minut

Sdílet

 Autor: Depositphotos
Seznámíme se se základními vlastnostmi nástroje nazvaného Dasel. Ten lze použít ke čtení informací ze souborů typu JSON, YAML, XML a TOML. To ovšem není vše, neboť soubory je možné nástrojem Dasel i modifikovat.

Obsah

1. Dasel – nástroj pro zpracování a modifikaci souborů JSON, YAML, XML a TOML

2. Instalace nástroje dasel ze zdrojových kódů

3. Čtení informací ze souborů s formátem TOML

4. Příklady jednoduchých dotazů

5. Čtení informací ze souborů s formátem YAML

6. Příklady jednoduchých dotazů

7. Filtrace na základě nepovinných atributů

8. Export výsledků do jiného formátu

9. Sekvence vs. seznam (pole) hodnot

10. Čtení informací ze souborů s formátem XML

11. Ukázkové příklady

12. Přidání nových atributů do souborů ve formátu TOML

13. Přidání globálního atributu

14. Přidání atributu do uloženého objektu

15. Vymazání celé sekce ze souboru typu TOML

16. Vymazání jednoho atributu ze souboru TOML

17. Zpracování tabulek uložených v souborech typu CSV

18. Transformace dat do CSV

19. Repositář s pracovními soubory

20. Odkazy na Internetu

1. Dasel – nástroj pro zpracování a modifikaci souborů JSON, YAML, XML a TOML

V praxi se velmi často setkáme s nutností čtení informací z konfiguračních souborů, popř. je nutné takové soubory nějakým způsobem modifikovat. Složitější konfigurační parametry se, zejména ve chvíli, kdy je nutné pracovat se strukturovanými daty a různými datovými typy, neukládají ani do souborů typu INI ani do .properties (ty se používají například ve světě Javy, ovšem jen v omezené míře). Namísto toho je možné využít například následující formáty:

  • JSON (JavaScript Object Notation) – pravděpodobně nejznámější formát, který byl sice určen pro přenosy dat (typicky mezi webovou službou/serverem a další službou nebo klientem), ovšem dnes se s tímto formátem setkáme i v dalších odvětvích – serializace, uložení strukturovaných dat do databáze a taktéž konfigurační soubory.
  • YAML (YAML Ain't Markup Language) – je formátem, který se namísto použití závorek pro určení struktury spoléhá spíše na použití odsazení (podobně, jako je tomu v Pythonu) a popř. i speciálních znaků (-, #, [, ] atd.). S tímto formátem se setkáme ve světě Dockeru a Kubernetes, ovšem je ho možné použít i pro další účely.
  • XML (Extensible Markup Language) – s tímto formátem pravděpodobně není nutné čtenáře tohoto článku podrobněji seznamovat. XML se pro uložení konfiguračních parametrů používá již delší dobu, i když se z některých důvodů nemusí vždy jednat o ideální řešení.
  • TOML (Tom's Obvious, Minimal Language) – formát TOML sice zdánlivě (alespoň na první pohled) vychází ze souborů typu INI, ovšem ve skutečnosti se jedná o odlišný, v mnoha ohledech vylepšený a především promyšlený formát, v němž byly odstraněny prakticky všechny nevýhody INI a přitom byla zachována čitelnost a snadnost úprav.
  • edn (Extensible Data Notation) – tento formát vychází ze syntaxe a sémantiky programovacího jazyka Clojure, je tedy založen na S-výrazech rozšířených o možnost zápisu map (slovníků) a vektorů. Formát edn je rozšířen právě v ekosystému jazyka Clojure, ale v ostatních oblastech se prozatím příliš nerozšířil, takže je dnes uveden spíše pro úplnost. Popis formátu edn (a tím pádem i popis syntaxe Clojure) naleznete na stránce https://github.com/edn-format/edn.
Poznámka: dnes popisovaný nástroj Dasel dokáže pracovat s prvními čtyřmi zmíněnými formáty, tedy konkrétně s formáty JSON, YAML, TOML a XML. Částečně je též podporován formát CSV, ovšem edn (alespoň prozatím) nikoli.

2. Instalace nástroje dasel ze zdrojových kódů

Dasel je naprogramován v jazyce Go, takže ve chvíli, kdy máte nainstalovány základní nástroje Go (překladač atd.), je stažení, překlad a instalace Daselu vyřešena jediným příkazem:

$ go install -v github.com/tomwright/dasel/v2/cmd/dasel@master

Během instalace se stahují i všechny závislé balíčky (transitive dependencies), kterých je v tomto případě celá řada, což je ostatně patrné i z následujícího výpisu:

go: downloading github.com/tomwright/dasel v1.27.4-0.20230111120636-c9187a46f74e
go: downloading github.com/tomwright/dasel/v2 v2.1.3-0.20230404173316-88bf4414bc0b
go: downloading github.com/spf13/cobra v1.6.1
go: downloading github.com/alecthomas/chroma v0.10.0
go: downloading github.com/clbanning/mxj/v2 v2.5.7
go: downloading golang.org/x/net v0.8.0
go: downloading github.com/dlclark/regexp2 v1.4.0
go: downloading golang.org/x/text v0.8.0
golang.org/x/text/internal/utf8internal
golang.org/x/net/html/atom
golang.org/x/text/encoding/internal/identifier
golang.org/x/text/transform
golang.org/x/text/internal/tag
github.com/tomwright/dasel/v2/internal
github.com/tomwright/dasel/v2
github.com/dlclark/regexp2/syntax
github.com/clbanning/mxj/v2
github.com/spf13/cobra
github.com/pelletier/go-toml
golang.org/x/net/html
golang.org/x/text/internal/language
golang.org/x/text/encoding
golang.org/x/text/encoding/internal
golang.org/x/text/encoding/charmap
golang.org/x/text/encoding/japanese
golang.org/x/text/encoding/korean
github.com/dlclark/regexp2
golang.org/x/text/encoding/simplifiedchinese
golang.org/x/text/encoding/traditionalchinese
golang.org/x/text/runes
golang.org/x/text/encoding/unicode
golang.org/x/text/internal/language/compact
golang.org/x/text/language
github.com/alecthomas/chroma
github.com/alecthomas/chroma/formatters/html
github.com/alecthomas/chroma/lexers/internal
github.com/alecthomas/chroma/formatters/svg
github.com/alecthomas/chroma/styles
github.com/alecthomas/chroma/lexers/a
github.com/alecthomas/chroma/lexers/b
github.com/alecthomas/chroma/formatters
github.com/alecthomas/chroma/lexers/p
github.com/alecthomas/chroma/lexers/j
github.com/alecthomas/chroma/lexers/e
github.com/alecthomas/chroma/lexers/f
github.com/alecthomas/chroma/lexers/i
github.com/alecthomas/chroma/lexers/d
github.com/alecthomas/chroma/lexers/c
github.com/alecthomas/chroma/lexers/k
github.com/alecthomas/chroma/lexers/l
github.com/alecthomas/chroma/lexers/n
github.com/alecthomas/chroma/lexers/o
github.com/alecthomas/chroma/lexers/q
github.com/alecthomas/chroma/lexers/r
github.com/alecthomas/chroma/lexers/t
github.com/alecthomas/chroma/lexers/v
github.com/alecthomas/chroma/lexers/w
github.com/alecthomas/chroma/lexers/x
github.com/alecthomas/chroma/lexers/y
github.com/alecthomas/chroma/lexers/z
github.com/alecthomas/chroma/lexers/h
github.com/alecthomas/chroma/lexers/circular
github.com/alecthomas/chroma/lexers/g
github.com/alecthomas/chroma/lexers/m
github.com/alecthomas/chroma/lexers/s
github.com/alecthomas/chroma/lexers
github.com/alecthomas/chroma/quick
golang.org/x/text/encoding/htmlindex
golang.org/x/net/html/charset
github.com/tomwright/dasel/v2/storage
github.com/tomwright/dasel/v2/internal/command
github.com/tomwright/dasel/v2/cmd/dasel

Výsledkem překladu by měl být spustitelný soubor dasel:

$ whereis -b dasel
 
dasel: /home/ptisnovs/go/bin/dasel

Jedná se přitom o pěkného „bumbrlíčka“, a to kvůli statickému slinkování s celou řadou tranzitivně závislých balíčků:

$ ls -l -h /home/ptisnovs/go/bin/dasel
 
-rwxrwxr-x 1 ptisnovs ptisnovs 14M Apr  8 08:40 /home/ptisnovs/go/bin/dasel
Poznámka: v dalším textu budeme předpokládat, že cesta ~/go/bin/dasel je umístěna na PATH a tedy že je možné Dasel spouštět z libovolného pracovního adresáře.

3. Čtení informací ze souborů s formátem TOML

V navazujících kapitolách si ukážeme základní funkce, které nástroj Dasel uživatelům nabízí, na reálných konfiguračních souborech uložených v několika podporovaných formátech. Začneme konfiguračním souborem, jenž používá formát TOML. Tento soubor naleznete na adrese https://github.com/tisnik/sli­des/blob/master/files/dasel/con­fig.toml:

[logging]
debug = true
log_level = "info"
logging_to_cloud_watch_enabled = false
 
[kafka_broker]
enabled = true
address = "kafka:29092" #provide in deployment env or as secret
security_protocol = "PLAINTEXT"
cert_path = "not-set"
sasl_mechanism = "PLAIN"
sasl_username = "not-used"
sasl_password = "not-used"
topic = "platform.notifications.ingress" #provide in deployment env or as secret
timeout = "60s"
likelihood_threshold = 0
impact_threshold = 0
severity_threshold = 0
total_risk_threshold = 2
event_filter = "totalRisk >= totalRiskThreshold"
tag_filter_enabled = false
tags = []
# valid units are SQL epoch time units: months days hours minutes seconds"
# set to empty string "" or 0 to disable
cooldown = "1 week"
 
[service_log]
client_id = "a-service-id"
client_secret = "a-secret"
created_by = "service-account-service"
username ="service-username"
token_url = ""
enabled = false
url = "https://api.foobar.com/api/service_logs/v1/"
timeout = "15s"
likelihood_threshold = 0
impact_threshold = 0
severity_threshold = 0
total_risk_threshold = 0
event_filter = "totalRisk >= totalRiskThreshold"
tag_filter_enabled = true
tags = ["osd_customer"]
# valid units are SQL epoch time units: months days hours minutes seconds"
# set to empty string "" or "0" to disable
cooldown = "1 week"
 
[storage]
db_driver = "postgres"
pg_username = "postgres" #provide in deployment env or as secret
pg_password = "postgres" #provide in deployment env or as secret
pg_host = "localhost" #provide in deployment env or as secret
pg_port = 5432 #provide in deployment env or as secret
pg_db_name = "notification" #provide in deployment env or as secret
pg_params = "sslmode=disable"
log_sql_queries = true
Poznámka: povšimněte si, že kromě rozdělení atributů do sekcí je možné do souborů TOML zapisovat i poznámky.

4. Příklady jednoduchých dotazů

Vyzkoušejme si nyní některé základní vlastnosti poskytované nástrojem Dasel. Dotazovací jazyk je do značné míry podobný jazyku, s nímž jsme se seznámili v článku o nástroji jq. Nejprve si selektorem . (tečka) necháme vypsat obsah celého souboru, přičemž se Dasel bude snažit o jeho naformátování:

$ dasel -f config.toml "."

Výsledek:

[kafka_broker]
  address = "kafka:29092"
  cert_path = "not-set"
  cooldown = "1 week"
  enabled = true
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  sasl_mechanism = "PLAIN"
  sasl_password = "not-used"
  sasl_username = "not-used"
  security_protocol = "PLAINTEXT"
  severity_threshold = 0
...
...
...
  total_risk_threshold = 0
  url = "https://api.foobar.com/api/service_logs/v1/"
  username = "service-username"
 
[storage]
  db_driver = "postgres"
  log_sql_queries = true
  pg_db_name = "notification"
  pg_host = "localhost"
  pg_params = "sslmode=disable"
  pg_password = "postgres"
  pg_port = 5432
  pg_username = "postgres"

Podporován je ovšem i obarvený výstup (pozor na zápis „colour“ a nikoli „color“):

$ dasel --colour -f config.toml "."

Výstup:

Obrázek 1: Obarvený výstup ve formátu TOML.

Dále se pokusíme o přečtení jedné konkrétní sekce:

$ dasel -f config.toml "service_log"

Výsledek:

client_id = "a-service-id"
client_secret = "a-secret"
cooldown = "1 week"
created_by = "service-account-service"
enabled = false
event_filter = "totalRisk >= totalRiskThreshold"
impact_threshold = 0
likelihood_threshold = 0
severity_threshold = 0
tag_filter_enabled = true
tags = ["osd_customer"]
timeout = "15s"
token_url = ""
total_risk_threshold = 0
url = "https://api.foobar.com/api/service_logs/v1/"
username = "service-username"

Přečíst můžeme i jeden vybraný atribut:

$ dasel -f config.toml "service_log.cooldown"
 
1 week

Přečtení dalšího atributu:

$ dasel -f config.toml "kafka_broker.address"
 
kafka:29092

A konečně se podívejme na výsledek snahy o přečtení neexistujícího atributu:

$ dasel -f config.toml kafka_broker.address.port
 
(nevypíše se nic, ani prázdný řádek)

5. Čtení informací ze souborů s formátem YAML

Velmi často se setkáme i s konfiguračními soubory ve formátu YAML. Příkladem může být následující soubor obsahující konfiguraci úlohy, která se spustí na straně GitHubu po každé změně obsahu repositáře. Tato úloha pro repositář vygeneruje stránky s dokumentací, které jsou poté dostupné přímo na GitHubu (takzvané GitHub pages). Tento konfigurační soubor lze nalézt na adrese https://github.com/tisnik/sli­des/blob/master/files/dasel/gh-pages.yml a jeho obsah vypadá následovně:

name: Build and deploy Jekyll site to GitHub Pages
 
on:
  push:
    branches:
      - master
 
jobs:
  github-pages:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Setup Go
        uses: actions/setup-go@v2
        with:
          go-version: '^1.14.1'
      - name: Generate docgo
        run: make godoc
      - name: Generate Jekyll site
        uses: helaili/jekyll-action@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          jekyll_src: 'docs'

6. Příklady jednoduchých dotazů

Opět si vyzkoušejme získat nějaké informace, tentokrát ze souboru ve formátu YAML. V prvním kroku se pokusíme o přeformátování souboru, tj. o výběr všech objektů a jejich atributů:

$ dasel -f gh-pages.yml '.'
 
jobs:
  github-pages:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    - name: Setup Go
      uses: actions/setup-go@v2
      with:
        go-version: ^1.14.1
    - name: Generate docgo
      run: make godoc
    - name: Generate Jekyll site
      uses: helaili/jekyll-action@v2
      with:
        jekyll_src: docs
        token: ${{ secrets.GITHUB_TOKEN }}
name: Build and deploy Jekyll site to GitHub Pages
"true":
  push:
    branches:
    - master

Obarvený výstup:

$ dasel --colour -f gh-pages.yml '.'

Obrázek 2: Obarvený výstup ve formátu YAML.

Poznámka: povšimněte si zajímavé „vlastnosti“ a současně i chyby – klíč „on“ byl změněn na „true“, což je vlastnost YAMLu 1.1 (dnes již sice stará verze, ovšem stále používaná), která je dodržována například i v knihovně PyYaml (viz též specifikaci).

Samozřejmě můžeme provést i výběr určitého objektu, popř. vybraného atributu tohoto objektu:

$ dasel -f gh-pages.yml ".jobs.github-pages.steps"
 
- name: Checkout
  uses: actions/checkout@v2
- name: Setup Go
  uses: actions/setup-go@v2
  with:
    go-version: ^1.14.1
- name: Generate docgo
  run: make godoc
- name: Generate Jekyll site
  uses: helaili/jekyll-action@v2
  with:
    jekyll_src: docs
    token: ${{ secrets.GITHUB_TOKEN }}

Získat můžeme například i jména všech kroků, a to následujícím způsobem – s využitím funkce all:

$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().name'
 
Checkout
Setup Go
Generate docgo
Generate Jekyll site

7. Filtrace na základě nepovinných atributů

Poměrně často se setkáme s nutností získání hodnot nějakých atributů, které však nejsou nastaveny pro všechny objekty v souborech YAML (TOML atd.). Příkladem může být atribut uses, který není nastaven vždy a proto pokus o přečtení hodnot tohoto atributu (atributů) nebude úspěšný:

$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().uses' -w json
 
(nic se nevypíše, tedy ani prázdný řádek)

V takovém případě musíme za jméno nepovinného atributu přidat otazník a celý dotaz tedy může vypadat takto:

$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().uses?' -w json

Výsledek již bude odpovídat očekávání:

"actions/checkout@v2"
"actions/setup-go@v2"
"helaili/jekyll-action@v2"

Tato vlastnost je podporována nejenom u formátu YAML, a i u dalších formátů, tedy i TOML apod. Opět si tedy tuto užitečnou vlastnost otestujeme:

$ dasel -f config.toml '.all().cooldown?'
 
1 week
1 week

8. Export výsledků do jiného formátu

Jednou z velmi užitečných vlastností nástroje Dasel je jeho schopnost provést export dat z jednoho podporovaného formátu do formátu jiného. Příkladem může být převod zprávy uložené původně ve formátu JSON do formátu TOML, který lze provést následovně (povšimněte si přepínače –pretty, jenž zajistí tisk výsledku v naformátované a dobře čitelné podobě):

$ dasel --pretty -f msg_with_schema.json -w toml
 
[payload]
  ID = 1.0
  Name = "Linus"
  Surname = "Torvalds"
 
[schema]
  fields = [{ field = "ID", optional = false, type = "int64" }, { field = "Name", optional = false, type = "string" }, { field = "Surname", optional = false, type = "string" }]
  optional = false
  type = "struct"
  version = 1.0
Poznámka: v současné verzi Daselu není podporován tisk atributu s více položkami na větší množství řádků, což je konkrétně patrné u atributu fields.

Další konverzí, kterou si ukážeme, je převod konfiguračního souboru z formátu YAML na formát JSON:

$ dasel -f gh-pages.yml -w json
 
{
  "jobs": {
    "github-pages": {
      "runs-on": "ubuntu-latest",
      "steps": [
        {
          "name": "Checkout",
          "uses": "actions/checkout@v2"
        },
        {
          "name": "Setup Go",
          "uses": "actions/setup-go@v2",
          "with": {
            "go-version": "^1.14.1"
          }
        },
        {
          "name": "Generate docgo",
          "run": "make godoc"
        },
        {
          "name": "Generate Jekyll site",
          "uses": "helaili/jekyll-action@v2",
          "with": {
            "jekyll_src": "docs",
            "token": "${{ secrets.GITHUB_TOKEN }}"
          }
        }
      ]
    }
  },
  "name": "Build and deploy Jekyll site to GitHub Pages",
  "true": {
    "push": {
      "branches": [
        "master"
      ]
    }
  }
}

9. Sekvence vs. seznam (pole) hodnot

Při použití nástroje Dasel je možné kombinovat selekci (filtraci) s výstupem do jiného (zvoleného) formátu. Například následující příkaz zajistí, že se vygeneruje seznam (pole) obsahující objekty typu „step“ (resp. přesněji řečeno jejich atributy):

$ dasel -f gh-pages.yml '.jobs.github-pages.steps' -w json

Nyní bude výsledek vypadat takto:

[
  {
    "name": "Checkout",
    "uses": "actions/checkout@v2"
  },
  {
    "name": "Setup Go",
    "uses": "actions/setup-go@v2",
    "with": {
      "go-version": "^1.14.1"
    }
  },
  {
    "name": "Generate docgo",
    "run": "make godoc"
  },
  {
    "name": "Generate Jekyll site",
    "uses": "helaili/jekyll-action@v2",
    "with": {
      "jekyll_src": "docs",
      "token": "${{ secrets.GITHUB_TOKEN }}"
    }
  }
]

V některých případech, typicky při použití funkce all() ovšem bude výsledkem ne jednotlivý objekt, ale sekvence objektů (ovšem nikoli pole nebo seznam):

$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all()' -w json
 
{
  "name": "Checkout",
  "uses": "actions/checkout@v2"
}
{
  "name": "Setup Go",
  "uses": "actions/setup-go@v2",
  "with": {
    "go-version": "^1.14.1"
  }
}
{
  "name": "Generate docgo",
  "run": "make godoc"
}
{
  "name": "Generate Jekyll site",
  "uses": "helaili/jekyll-action@v2",
  "with": {
    "jekyll_src": "docs",
    "token": "${{ secrets.GITHUB_TOKEN }}"
  }
}
Poznámka: nejedná se tedy o validní JSON; spíše o sekvenci validních JSONů.

Můžeme jít ještě dále a získat sekvenci řetězcových atributů (opět se nejedná o validní JSON):

$ dasel -f gh-pages.yml '.jobs.github-pages.steps.all().name' -w json
 
"Checkout"
"Setup Go"
"Generate docgo"
"Generate Jekyll site"

Podobný příklad, ovšem nyní vracející obsah původních objektů uložených za sebou v sekvenci:

$ dasel -f config.toml '.all()' -w json
 
{
  "client_id": "a-service-id",
  "client_secret": "a-secret",
  "cooldown": "1 week",
  "created_by": "service-account-service",
  "enabled": false,
  "event_filter": "totalRisk >= totalRiskThreshold",
  "impact_threshold": 0,
  "likelihood_threshold": 0,
  "severity_threshold": 0,
  "tag_filter_enabled": true,
  "tags": [
    "osd_customer"
  ],
  "timeout": "15s",
  "token_url": "",
  "total_risk_threshold": 0,
  "url": "https://api.foobar.com/api/service_logs/v1/",
  "username": "service-username"
}
{
  "db_driver": "postgres",
  "log_sql_queries": true,
  "pg_db_name": "notification",
  "pg_host": "localhost",
  "pg_params": "sslmode=disable",
  "pg_password": "postgres",
  "pg_port": 5432,
  "pg_username": "postgres"
}
{
  "debug": true,
  "log_level": "info",
  "logging_to_cloud_watch_enabled": false
}
{
  "address": "kafka:29092",
  "cert_path": "not-set",
  "cooldown": "1 week",
  "enabled": true,
  "event_filter": "totalRisk >= totalRiskThreshold",
  "impact_threshold": 0,
  "likelihood_threshold": 0,
  "sasl_mechanism": "PLAIN",
  "sasl_password": "not-used",
  "sasl_username": "not-used",
  "security_protocol": "PLAINTEXT",
  "severity_threshold": 0,
  "tag_filter_enabled": false,
  "tags": [],
  "timeout": "60s",
  "topic": "platform.notifications.ingress",
  "total_risk_threshold": 2
}

10. Čtení informací ze souborů s formátem XML

Zpracovávat lze pochopitelně i data uložená do formátu XML. Pro ilustraci použijeme soubor, který byl vytvořen nástrojem SchemaSpy a obsahuje popis schématu relační databáze. Pro účely dnešního článku byl soubor zkrácen tak, že popisuje pouze několik tabulek a nikoli celé schéma. Jeho obsah naleznete na adrese https://github.com/tisnik/sli­des/blob/master/files/dasel/no­tification.public.xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<database name="notification" schema="public" type="PostgreSQL - 9.6.10">
   <sequences>
      <sequence increment="1" name="read_errors_error_id_seq" startValue="1"/>
   </sequences>
   <tables>
      <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE">
         <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4">
            <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="name" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="metainfo" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="event_targets_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
         <index name="event_targets_metainfo_key" unique="true">
            <column ascending="true" name="metainfo"/>
         </index>
         <index name="event_targets_name_key" unique="true">
            <column ascending="true" name="name"/>
         </index>
      </table>
      <table name="migration_info" numRows="1" remarks="information about the latest DB schema and migration status" schema="public" type="TABLE">
         <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="version" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
      </table>
      <table name="states" numRows="4" remarks="states for each row stored in reported table" schema="public" type="TABLE">
         <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="XXXXXXXXX" size="10" type="int4" typeCode="4">
            <child column="state" foreignKey="fk_state" implied="false" onDeleteCascade="false" schema="public" table="reported"/>
         </column>
         <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="value" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
         <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="comment" nullable="true" remarks="" size="2147483647" type="varchar" typeCode="12"/>
         <primaryKey column="id" sequenceNumberInPK="1"/>
         <index name="states_pkey" unique="true">
            <column ascending="true" name="id"/>
         </index>
      </table>
   </tables>
</database>

11. Ukázkové příklady

Podívejme se nyní na několik demonstračních příkladů, které ukazují způsob zpracování XML souborů.

Pouze pretty-printing obsahu celého souboru:

$ dasel -f notification.public.xml "."
 
<database name="notification" schema="public" type="PostgreSQL - 9.6.10">
  <sequences>
    <sequence increment="1" name="read_errors_error_id_seq" startValue="1"/>
  </sequences>
  <tables>
    <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE">
      <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4">
        <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/>
      </column>
      ...
      ...
      ...

Pretty printing s obarvením syntaxe:

$ dasel --colour -f notification.public.xml "."

Obrázek 3: Obarvený výstup ve formátu XML.

Získání informací o tabulkách, nikoli o dalších objektech uložených v databázi (sekvence atd.). Povšimněte si, že aby vznikl korektní XML strom, bylo nutné přidat uzel doc:

$ dasel -f notification.public.xml ".database.tables"
 
<doc>
  <table name="event_targets" numRows="2" remarks="specification of all event targets currently supported" schema="public" type="TABLE">
    <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="" size="10" type="int4" typeCode="4">
      <child column="event_type_id" foreignKey="reported_event_type_id_fkey" implied="false" onDeleteCascade="false" schema="public" table="reported"/>
    </column>
    <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="name" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
    <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="metainfo" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
    <index name="event_targets_pkey" unique="true">
      <column ascending="true" name="id"/>
    </index>
    <index name="event_targets_metainfo_key" unique="true">
      <column ascending="true" name="metainfo"/>
    </index>
    <index name="event_targets_name_key" unique="true">
      <column ascending="true" name="name"/>
    </index>
    <primaryKey column="id" sequenceNumberInPK="1"/>
  </table>
  <table name="migration_info" numRows="1" remarks="information about the latest DB schema and migration status" schema="public" type="TABLE">
    <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="version" nullable="false" remarks="" size="10" type="int4" typeCode="4"/>
  </table>
  <table name="states" numRows="4" remarks="states for each row stored in reported table" schema="public" type="TABLE">
    <column autoUpdated="false" defaultValue="null" digits="0" id="0" name="id" nullable="false" remarks="XXXXXXXXX" size="10" type="int4" typeCode="4">
      <child column="state" foreignKey="fk_state" implied="false" onDeleteCascade="false" schema="public" table="reported"/>
    </column>
    <column autoUpdated="false" defaultValue="null" digits="0" id="1" name="value" nullable="false" remarks="" size="2147483647" type="varchar" typeCode="12"/>
    <column autoUpdated="false" defaultValue="null" digits="0" id="2" name="comment" nullable="true" remarks="" size="2147483647" type="varchar" typeCode="12"/>
    <index name="states_pkey" unique="true">
      <column ascending="true" name="id"/>
    </index>
    <primaryKey column="id" sequenceNumberInPK="1"/>
  </table>
</doc>

Pokus o převod do formátu JSON, což není přímočará transformace, neboť je nutné pracovat i s atributy uzlů. Povšimněte si, jak jsou odlišeny původní atributy od poduzlů:

$ dasel -f notification.public.xml -w json | more
 
{
  "database": {
    "-name": "notification",
    "-schema": "public",
    "-type": "PostgreSQL - 9.6.10",
    "sequences": {
      "sequence": {
        "-increment": "1",
        "-name": "read_errors_error_id_seq",
        "-startValue": "1"
      }
    },
    "tables": {
      "table": [
        {
          "-name": "event_targets",
          "-numRows": "2",
          "-remarks": "specification of all event targets currently supported",
          "-schema": "public",
          "-type": "TABLE",
          "column": [
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "0",
              "-name": "id",
              "-nullable": "false",
              "-remarks": "",
              "-size": "10",
              "-type": "int4",
              "-typeCode": "4",
              "child": {
                "-column": "event_type_id",
                "-foreignKey": "reported_event_type_id_fkey",
                "-implied": "false",
                "-onDeleteCascade": "false",
                "-schema": "public",
                "-table": "reported"
              }
            },
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "1",
              "-name": "name",
              "-nullable": "false",
              "-remarks": "",
              "-size": "2147483647",
              "-type": "varchar",
              "-typeCode": "12"
            },
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "2",
              "-name": "metainfo",
              "-nullable": "false",
              "-remarks": "",
              "-size": "2147483647",
              "-type": "varchar",
              "-typeCode": "12"
            }
          ],
          "index": [
            {
              "-name": "event_targets_pkey",
              "-unique": "true",
              "column": {
                "-ascending": "true",
                "-name": "id"
              }
            },
            {
              "-name": "event_targets_metainfo_key",
              "-unique": "true",
              "column": {
                "-ascending": "true",
                "-name": "metainfo"
              }
            },
            {
              "-name": "event_targets_name_key",
              "-unique": "true",
              "column": {
                "-ascending": "true",
                "-name": "name"
              }
            }
          ],
          "primaryKey": {
            "-column": "id",
            "-sequenceNumberInPK": "1"
          }
        },
        {
          "-name": "migration_info",
          "-numRows": "1",
          "-remarks": "information about the latest DB schema and migration status",
          "-schema": "public",
          "-type": "TABLE",
          "column": {
            "-autoUpdated": "false",
            "-defaultValue": "null",
            "-digits": "0",
            "-id": "0",
            "-name": "version",
            "-nullable": "false",
            "-remarks": "",
            "-size": "10",
            "-type": "int4",
            "-typeCode": "4"
          }
        },
        {
          "-name": "states",
          "-numRows": "4",
          "-remarks": "states for each row stored in reported table",
          "-schema": "public",
          "-type": "TABLE",
          "column": [
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "0",
              "-name": "id",
              "-nullable": "false",
              "-remarks": "XXXXXXXXX",
              "-size": "10",
              "-type": "int4",
              "-typeCode": "4",
              "child": {
                "-column": "state",
                "-foreignKey": "fk_state",
                "-implied": "false",
                "-onDeleteCascade": "false",
                "-schema": "public",
                "-table": "reported"
              }
            },
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "1",
              "-name": "value",
              "-nullable": "false",
              "-remarks": "",
              "-size": "2147483647",
              "-type": "varchar",
              "-typeCode": "12"
            },
            {
              "-autoUpdated": "false",
              "-defaultValue": "null",
              "-digits": "0",
              "-id": "2",
              "-name": "comment",
              "-nullable": "true",
              "-remarks": "",
              "-size": "2147483647",
              "-type": "varchar",
              "-typeCode": "12"
            }
          ],
          "index": {
            "-name": "states_pkey",
            "-unique": "true",
            "column": {
              "-ascending": "true",
              "-name": "id"
            }
          },
          "primaryKey": {
            "-column": "id",
            "-sequenceNumberInPK": "1"
          }
        }
      ]
    }
  }
}

12. Přidání nových atributů do souborů ve formátu TOML

Velmi užitečnou vlastností, kterou nástroj Dasel uživatelům nabízí, je možnost přidání dalších atributů do zpracovávaných datových či konfiguračních souborů. Pro tento účel slouží příkaz nazvaný put, přičemž je nutné přepínačem -t specifikovat typ atributu a přepínačem -v jeho hodnotu, popř. dvojici hodnota+klíč (v tomto pořadí!). Mezi základní podporované typy patří skalární typy bool, int a number (v podstatě typ float) a referenční typy array a object.

13. Přidání globálního atributu

Příkaz put mění obsah zpracovávaného souboru, takže si nejdříve vytvoříme jeho kopii:

$ cp config.toml config2.toml

Do souboru config2.toml nyní přidáme nový globální atribut nazvaný „priority_threshold“ a nastavíme mu celočíselnou hodnotu 1:

$ dasel put -f config2.toml -t int -v 1 "priority_threshold"

Výsledkem výše uvedeného příkazu bude modifikovaný soubor config2.toml, který bude skutečně obsahovat nový globální atribut (zobrazený je hned na prvním řádku):

priority_threshold = 1
 
[kafka_broker]
  address = "kafka:29092"
  cert_path = "not-set"
  cooldown = "1 week"
  enabled = true
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  sasl_mechanism = "PLAIN"
  sasl_password = "not-used"
  sasl_username = "not-used"
  security_protocol = "PLAINTEXT"
  severity_threshold = 0
  tag_filter_enabled = false
  tags = []
  timeout = "60s"
  topic = "platform.notifications.ingress"
  total_risk_threshold = 2
 
[logging]
  debug = true
  log_level = "info"
  logging_to_cloud_watch_enabled = false
 
[service_log]
  client_id = "a-service-id"
  client_secret = "a-secret"
  cooldown = "1 week"
  created_by = "service-account-service"
  enabled = false
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  severity_threshold = 0
  tag_filter_enabled = true
  tags = ["osd_customer"]
  timeout = "15s"
  token_url = ""
  total_risk_threshold = 0
  url = "https://api.foobar.com/api/service_logs/v1/"
  username = "service-username"
 
[storage]
  db_driver = "postgres"
  log_sql_queries = true
  pg_db_name = "notification"
  pg_host = "localhost"
  pg_params = "sslmode=disable"
  pg_password = "postgres"
  pg_port = 5432
  pg_username = "postgres"

14. Přidání atributu do uloženého objektu

Samozřejmě je možné přidat nový atribut do již existujícího souboru. V tomto případě se použije stejná syntaxe, jako při výběru (selekci) atributu či atributů – využije se klasická „tečková notace“. Vše je ukázáno na následujícím demonstračním příkladu, v němž opět dojde k přidání atributu nazvaného „priority_threshold“ s hodnotou 1, tentokrát ovšem nikoli na globální úrovni, ale do objektu „service_log“:

$ cp config.toml config3.toml
$ dasel put -f config3.toml -t int -v 42 "service_log.priority_threshold"

Výsledný soubor config3.toml by měl vypadat následovně. Nový atribut je zvýrazněn:

[kafka_broker]
  address = "kafka:29092"
  cert_path = "not-set"
  cooldown = "1 week"
  enabled = true
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  sasl_mechanism = "PLAIN"
  sasl_password = "not-used"
  sasl_username = "not-used"
  security_protocol = "PLAINTEXT"
  severity_threshold = 0
  tag_filter_enabled = false
  tags = []
  timeout = "60s"
  topic = "platform.notifications.ingress"
  total_risk_threshold = 2
 
[logging]
  debug = true
  log_level = "info"
  logging_to_cloud_watch_enabled = false
 
[service_log]
  client_id = "a-service-id"
  client_secret = "a-secret"
  cooldown = "1 week"
  created_by = "service-account-service"
  enabled = false
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  priority_threshold = 42
  severity_threshold = 0
  tag_filter_enabled = true
  tags = ["osd_customer"]
  timeout = "15s"
  token_url = ""
  total_risk_threshold = 0
  url = "https://api.foobar.com/api/service_logs/v1/"
  username = "service-username"
 
[storage]
  db_driver = "postgres"
  log_sql_queries = true
  pg_db_name = "notification"
  pg_host = "localhost"
  pg_params = "sslmode=disable"
  pg_password = "postgres"
  pg_port = 5432
  pg_username = "postgres"

15. Vymazání celé sekce ze souboru typu TOML

Vzhledem k existenci operace put určené pro vložení nového atributu do zpracovávaných souborů nás pravděpodobně nepřekvapí, že Dasel podporuje i operaci delete, která nějaký atribut či celý objekt naopak vymaže. V tomto případě se pochopitelně specifikuje pouze selektor objektu či atributu, nikoli hodnota. Pokusme se například vymazat celý objekt „service_log“:

$ cp config.toml config4.toml
$ dasel delete -f config4.toml "service_log"

Výsledkem bude tento zkrácený soubor:

[kafka_broker]
  address = "kafka:29092"
  cert_path = "not-set"
  cooldown = "1 week"
  enabled = true
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  sasl_mechanism = "PLAIN"
  sasl_password = "not-used"
  sasl_username = "not-used"
  security_protocol = "PLAINTEXT"
  severity_threshold = 0
  tag_filter_enabled = false
  tags = []
  timeout = "60s"
  topic = "platform.notifications.ingress"
  total_risk_threshold = 2
 
[logging]
  debug = true
  log_level = "info"
  logging_to_cloud_watch_enabled = false
 
[storage]
  db_driver = "postgres"
  log_sql_queries = true
  pg_db_name = "notification"
  pg_host = "localhost"
  pg_params = "sslmode=disable"
  pg_password = "postgres"
  pg_port = 5432
  pg_username = "postgres"

16. Vymazání jednoho atributu ze souboru TOML

Nástroj Dasel pochopitelně umožňuje i vymazání jediného atributu, což je opět vlastnost, kterou si nejlépe ukážeme na souboru config.toml. Nejprve si vytvoříme kopii tohoto souboru a posléze z v něm uložených dat vymažeme atribut „debug“ objektu „logging“:

$ cp config.toml config5.toml
$ dasel delete -f config5.toml "logging.debug"

Výsledek by měl vypadat následovně:

[kafka_broker]
  address = "kafka:29092"
  cert_path = "not-set"
  cooldown = "1 week"
  enabled = true
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  sasl_mechanism = "PLAIN"
  sasl_password = "not-used"
  sasl_username = "not-used"
  security_protocol = "PLAINTEXT"
  severity_threshold = 0
  tag_filter_enabled = false
  tags = []
  timeout = "60s"
  topic = "platform.notifications.ingress"
  total_risk_threshold = 2
 
[logging]
  log_level = "info"
  logging_to_cloud_watch_enabled = false
 
[service_log]
  client_id = "a-service-id"
  client_secret = "a-secret"
  cooldown = "1 week"
  created_by = "service-account-service"
  enabled = false
  event_filter = "totalRisk >= totalRiskThreshold"
  impact_threshold = 0
  likelihood_threshold = 0
  severity_threshold = 0
  tag_filter_enabled = true
  tags = ["osd_customer"]
  timeout = "15s"
  token_url = ""
  total_risk_threshold = 0
  url = "https://api.foobar.com/api/service_logs/v1/"
  username = "service-username"
 
[storage]
  db_driver = "postgres"
  log_sql_queries = true
  pg_db_name = "notification"
  pg_host = "localhost"
  pg_params = "sslmode=disable"
  pg_password = "postgres"
  pg_port = 5432
  pg_username = "postgres"

17. Zpracování tabulek uložených v souborech typu CSV

Teoreticky by měl nástroj Dasel podporovat i práci se soubory typu CSV, takže by se mělo jednat o alternativu k nástroji Q (i když dotazovací jazyk Q odvozený od SQL je podle mého názoru mnohem sofistikovanější). Nicméně import CSV nemusí být vždy funkční. Můžeme si to ostatně ukázat na následujícím souboru:

Year,Winner
2022,C++
2021,Python
2020,Python
2019,C
2018,Python
2017,C
2016,Go
2015,Java
2014,JavaScript
2013,Transact-SQL
2012,Objective-C
2011,Objective-C
2010,Python
2009,Go
2008,C
2007,Python
2006,Ruby
2005,Java
2004,PHP
2003,C++

Při pokusu o načtení tohoto souboru dojde pouze k lakonickému zobrazení chyby a k následnému ukončení Daselu:

$ dasel -f hall_of_fame.csv
 
Error: CSVParser.toBytes cannot handle type []map[string]interface {}

18. Transformace dat do CSV

Na druhou stranu je však možné data transformovat do CSV, což je výhodné pouze tehdy, pokud informace uložené v TOML, YAML, XML či JSONu obsahují opakující se typy hodnot. Například takto lze vyexportovat informace o atributech z JSON schématu:

bitcoin_skoleni

$ dasel -f msg_with_schema.json -w csv "schema.fields"
 
field,optional,type
ID,false,int64
Name,false,string
Surname,false,string

Nepatrně složitější příklad:

$ dasel -f notification.public.xml -w csv "database.tables.table"
 
-name,-numRows,-remarks,-schema,-type,column,index,primaryKey
event_targets,2,specification of all event targets currently supported,public,TABLE,[map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:id -nullable:false -remarks: -size:10 -type:int4 -typeCode:4 child:map[-column:event_type_id -foreignKey:reported_event_type_id_fkey -implied:false -onDeleteCascade:false -schema:public -table:reported]] map[-autoUpdated:false -defaultValue:null -digits:0 -id:1 -name:name -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12] map[-autoUpdated:false -defaultValue:null -digits:0 -id:2 -name:metainfo -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12]],[map[-name:event_targets_pkey -unique:true column:map[-ascending:true -name:id]] map[-name:event_targets_metainfo_key -unique:true column:map[-ascending:true -name:metainfo]] map[-name:event_targets_name_key -unique:true column:map[-ascending:true -name:name]]],map[-column:id -sequenceNumberInPK:1]
migration_info,1,information about the latest DB schema and migration status,public,TABLE,map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:version -nullable:false -remarks: -size:10 -type:int4 -typeCode:4],,
states,4,states for each row stored in reported table,public,TABLE,[map[-autoUpdated:false -defaultValue:null -digits:0 -id:0 -name:id -nullable:false -remarks:XXXXXXXXX -size:10 -type:int4 -typeCode:4 child:map[-column:state -foreignKey:fk_state -implied:false -onDeleteCascade:false -schema:public -table:reported]] map[-autoUpdated:false -defaultValue:null -digits:0 -id:1 -name:value -nullable:false -remarks: -size:2147483647 -type:varchar -typeCode:12] map[-autoUpdated:false -defaultValue:null -digits:0 -id:2 -name:comment -nullable:true -remarks: -size:2147483647 -type:varchar -typeCode:12]],map[-name:states_pkey -unique:true column:map[-ascending:true -name:id]],map[-column:id -sequenceNumberInPK:1]

19. Repositář s pracovními soubory

Pracovní soubory ve formátu TOML, YAML, JSON, XML a CSV byly uloženy do repositáře, jenž je dostupný na adrese https://github.com/tisnik/slides/. V případě, že nebudete chtít klonovat celý repositář, můžete namísto toho použít odkazy na jednotlivé soubory, které naleznete v následující tabulce:

# Soubor Stručný popis Adresa
1 config.toml konfigurační soubor uložený ve formátu TOML https://github.com/tisnik/sli­des/blob/master/files/dasel/con­fig.toml
2 config3.toml upravený konfigurační soubor vygenerovaný nástrojem Dasel https://github.com/tisnik/sli­des/blob/master/files/dasel/con­fig3.toml
3 config4.toml upravený konfigurační soubor vygenerovaný nástrojem Dasel https://github.com/tisnik/sli­des/blob/master/files/dasel/con­fig4.toml
4 config5.toml upravený konfigurační soubor vygenerovaný nástrojem Dasel https://github.com/tisnik/sli­des/blob/master/files/dasel/con­fig5.toml
5 gh-pages.yml upravený konfigurační soubor vygenerovaný nástrojem Dasel https://github.com/tisnik/sli­des/blob/master/files/dasel/gh-pages.yml
6 msg_with_schema.json datový soubor uložený ve formátu JSON https://github.com/tisnik/sli­des/blob/master/files/dasel/msg_wit­h_schema.json
7 notification.public.xml konfigurační soubor uložený ve formátu XML https://github.com/tisnik/sli­des/blob/master/files/dasel/no­tification.public.xml
8 hall_of_fame.csv tabulka uložená ve formátu CSV https://github.com/tisnik/sli­des/blob/master/files/dasel/ha­ll_of_fame.csv

20. Odkazy na Internetu

  1. Comma-Separated Values
    https://en.wikipedia.org/wiki/Comma-separated_values
  2. Tab-Separated Values
    https://en.wikipedia.org/wiki/Tab-separated_values
  3. Delimiter-separated values
    https://en.wikipedia.org/wi­ki/Delimiter-separated_values
  4. q – Run SQL directly on CSV or TSV files
    https://harelba.github.io/q/
  5. q – examples
    https://harelba.github.io/q/#examples
  6. Repositář projektu q (GitHub)
    https://github.com/harelba/q
  7. How to run SQL queries directly on CSV or TSV file
    https://computingforgeeks.com/run-sql-queries-directly-on-csv-files/
  8. Tab separated values
    https://datatables.net/ex­tensions/buttons/examples/flash/tsv­.html
  9. csvkit 1.0.5 (dokumentace)
    https://csvkit.readthedoc­s.io/en/latest/#
  10. Repositář projektu csvkit (GitHub)
    https://github.com/wireservice/csvkit
  11. Příklad CSV schématu
    https://github.com/wireser­vice/ffs/blob/master/us/ir­s/irs_exempt_org_schema.csv
  12. Repositář projektu jq (GitHub)
    https://github.com/stedolan/jq
  13. GitHub stránky projektu jq
    https://stedolan.github.io/jq/
  14. 5 modern alternatives to essential Linux command-line tools
    https://opensource.com/ar­ticle/20/6/modern-linux-command-line-tools
  15. Návod k nástroji jq
    https://stedolan.github.i­o/jq/tutorial/
  16. jq Manual (development version)
    https://stedolan.github.io/jq/manual/
  17. Introducing JSON
    https://www.json.org/json-en.html
  18. jq.py: a lightweight and flexible JSON processor
    https://github.com/mwilliamson/jq.py
  19. Discover how to use jq, a JSON manipulation command line, with GeoJSON
    https://webgeodatavore.com/jq-json-manipulation-command-line-with-geojson.html
  20. Reshaping JSON with jq
    https://programminghistori­an.org/en/lessons/json-and-jq
  21. Python bindings for jq
    https://pypi.org/project/jq/
  22. edn
    https://github.com/edn-format/edn
  23. Why use JSON over XML?
    https://www.sitepoint.com/json-vs-xml/
  24. XML and XPath
    https://www.w3schools.com/XML/xml_xpat­h.asp
  25. XPath (Wikipedia)
    https://en.wikipedia.org/wiki/XPath
  26. RFC7159
    https://www.ietf.org/rfc/rfc7159.txt
  27. The Art of Unix Programming – DSV Style
    https://www.linuxtopia.or­g/online_books/programmin­g_books/art_of_unix_program­ming/ch05s02.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.