Поиск по сайту:

Разбор файлов конфигурации JSON с помощью Groovy


Отойдите от споров о том, следует ли использовать JSON в качестве формата конфигурации, и просто научитесь анализировать его с помощью Groovy.

Приложения обычно включают в себя некое состояние или конфигурацию по умолчанию или «готовую к использованию», а также возможность пользователям настраивать эту конфигурацию в соответствии со своими потребностями.

Например, LibreOffice Writer предоставляет доступ к таким данным, как пользовательские данные, шрифты, языковые настройки и (многое) другое, через Инструменты > Параметры в строке меню. Некоторые приложения (например, LibreOffice) предоставляют пользовательский интерфейс «укажи и щелкни» для управления этими настройками. Некоторые, например Tracker (задача GNOME, индексирующая файлы), используют файлы XML. А некоторые, особенно приложения на основе JavaScript, используют JSON, несмотря на протесты многих (например, этого автора и другого автора).

В этой статье я отойду от дискуссии о том, следует ли использовать JSON в качестве формата файла конфигурации, и объясню, как анализировать такого рода информацию с помощью языка программирования Groovy. Groovy основан на Java, но с другим набором приоритетов проектирования, благодаря которым Groovy больше похож на Python.

Установить Groovy

Поскольку Groovy основан на Java, для него также требуется установка Java. Вы можете найти последние и достойные версии Java и Groovy в репозиториях вашего дистрибутива Linux. Или вы можете установить Groovy, следуя инструкциям на его сайте. Хорошей альтернативой для пользователей Linux является SDKMan, с помощью которого можно получить несколько версий Java, Groovy и многих других связанных инструментов. В этой статье я буду использовать версию OpenJDK11 моего дистрибутива и версию SDKMan Groovy 3.0.7.

Демонстрационный файл конфигурации JSON

Для этой демонстрации я взял этот JSON из Drupal — это основной файл конфигурации, используемый Drupal CMS, — и сохранил его в файле config.json:

{
  "vm": {
    "ip": "192.168.44.44",
    "memory": "1024",
    "synced_folders": [
      {
        "host_path": "data/",
        "guest_path": "/var/www",
        "type": "default"
      }
    ],
    "forwarded_ports": []
  },
  "vdd": {
    "sites": {
      "drupal8": {
        "account_name": "root",
        "account_pass": "root",
        "account_mail": "box@example.com",
        "site_name": "Drupal 8",
        "site_mail": "box@example.com",
        "vhost": {
          "document_root": "drupal8",
          "url": "drupal8.dev",
          "alias": ["www.drupal8.dev"]
        }
      },
      "drupal7": {
        "account_name": "root",
        "account_pass": "root",
        "account_mail": "box@example.com",
        "site_name": "Drupal 7",
        "site_mail": "box@example.com",
        "vhost": {
          "document_root": "drupal7",
          "url": "drupal7.dev",
          "alias": ["www.drupal7.dev"]
        }
      }
    }
  }
}

Это красивый и сложный файл JSON с несколькими уровнями структуры, например:

<>.vdd.sites.drupal8.account_name

и некоторые списки, такие как:

<>.vm.synced_folders

Здесь <> представляет безымянный верхний уровень. Давайте посмотрим, как Groovy с этим справится.

Разбор JSON с помощью Groovy

Groovy поставляется с пакетом groovy.json, в котором полно всяких интересных вещей. Одной из лучших частей является класс JsonSlurper, который включает в себя несколько методов parse(), преобразующих JSON в Groovy Map — структуру данных с значения, хранящиеся в ключах.

Вот хорошая короткая программа на Groovy с именем config1.groovy, которая создает экземпляр JsonSlurper, а затем вызывает один из своих методов parse() для анализа JSON в файле и преобразуйте его в экземпляр Map с именем config и, наконец, записывает эту карту:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

def config = jsonSlurper.parse(new File('config.json'))

println "config = $config"

Запустите эту программу в командной строке терминала:

$ groovy config1.groovy
config = [vm:[ip:192.168.44.44, memory:1024, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 8, site_mail:box@example.com, vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 7, site_mail:box@example.com, vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
$

В выводе показана карта верхнего уровня с двумя ключами: vm и vdd. Каждый ключ ссылается на свою собственную карту значений. Обратите внимание на пустой список, на который ссылается ключ forwarded_ports.

Хм. Это было легко, но все, что нужно было сделать, это распечатать что-нибудь. Как вы получаете доступ к различным компонентам? Вот еще одна программа, показывающая, как получить доступ к значению, хранящемуся в config.vm.ip:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

def config = jsonSlurper.parse(new File('config.json'))

println "config.vm.ip = ${config.vm.ip}"

Запустить его:

$ groovy config2.groovy
config.vm.ip = 192.168.44.44
$

Да, это тоже легко. При этом используется сокращение Groovy, что означает:

config.vm.ip

в Groovy эквивалентно:

config['vm']['ip']

когда config и config.vm являются экземплярами Map и оба эквивалентны:

config.get("vm").get("ip")

на Яве.

Вот и все, что касается обработки JSON. Что, если вы хотите иметь стандартную конфигурацию и позволить пользователю ее переопределить? В этом случае вы можете захотеть жестко запрограммировать конфигурацию JSON в программе, а затем прочитать пользовательскую конфигурацию и переопределить любые стандартные параметры конфигурации.

Предположим, что приведенная выше конфигурация является стандартной, и пользователь хочет переопределить лишь ее часть, а именно значения ip и memory в структуре vm. и поместите это в файл userConfig.json:

{
  "vm": {
    "ip": "201.201.201.201",
    "memory": "4096",
  }
}

Вы можете сделать это с помощью этой программы:

import groovy.json.JsonSlurper

def jsonSlurper = new JsonSlurper()

// use parseText() to parse a string rather than reading from a file
// this gives us the “standard configuration”

def standardConfig = jsonSlurper.parseText("""
{
  "vm": {
    "ip": "192.168.44.44",
    "memory": "1024",
    "synced_folders": [
      {
        "host_path": "data/",
        "guest_path": "/var/www",
        "type": "default"
      }
    ],
    "forwarded_ports": []
  },
  "vdd": {
    "sites": {
      "drupal8": {
        "account_name": "root",
        "account_pass": "root",
        "account_mail": "box@example.com",
        "site_name": "Drupal 8",
        "site_mail": "box@example.com",
        "vhost": {
          "document_root": "drupal8",
          "url": "drupal8.dev",
          "alias": ["www.drupal8.dev"]
        }
      },
      "drupal7": {
        "account_name": "root",
        "account_pass": "root",
        "account_mail": "box@example.com",
        "site_name": "Drupal 7",
        "site_mail": "box@example.com",
        "vhost": {
          "document_root": "drupal7",
          "url": "drupal7.dev",
          "alias": ["www.drupal7.dev"]
        }
      }
    }
  }
}
""")

// print out the standard configuration

println "standardConfig = $standardConfig"

// read in and parse the user configuration information

def userConfig = jsonSlurper.parse(new File('userConfig.json'))

// print out the user configuration information

println "userConfig = $userConfig"

// a function to merge the user configuration with the standard

def mergeMaps(Map input, Map merge) {
    merge.each { k, v ->
        if (v instanceof Map)
            mergeMaps(input[k], v)
        else
            input[k] = v
    }
}

// merge the configurations and print out the modified
// standard configuration

mergeMaps(standardConfig, userConfig)

println "modified standardConfig $standardConfig"

Запустите это как:

$ groovy config3.groovy
standardConfig = [vm:[ip:192.168.44.44, memory:1024, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 8, site_mail:box@example.com, vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 7, site_mail:box@example.com, vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
userConfig = [vm:[ip:201.201.201.201, memory:4096]]
modified standardConfig [vm:[ip:201.201.201.201, memory:4096, synced_folders:[[host_path:data/, guest_path:/var/www, type:default]], forwarded_ports:[]], vdd:[sites:[drupal8:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 8, site_mail:box@example.com, vhost:[document_root:drupal8, url:drupal8.dev, alias:[www.drupal8.dev]]], drupal7:[account_name:root, account_pass:root, account_mail:box@example.com, site_name:Drupal 7, site_mail:box@example.com, vhost:[document_root:drupal7, url:drupal7.dev, alias:[www.drupal7.dev]]]]]]
$

Строка, начинающаяся с modifiedstandardConfig, показывает, что значения vm.ip и vm.memory были переопределены.

Внимательные читатели заметят, что я не проверял наличие неправильного формата JSON и не старался убедиться, что пользовательская конфигурация имеет смысл (не создает новые поля, предоставляет разумные значения и т. д.). Так что милый маленький рекурсивный метод объединения двух карт, вероятно, не так уж и практичен в реальном мире.

Ну, мне пришлось оставить кое-что для домашнего задания, не так ли?

Отличные ресурсы

На сайте Apache Groovy имеется много отличной документации. Еще один замечательный ресурс по Groovy — Mr. Haki. И действительно веская причина изучить Groovy — это продолжить изучение Grails, удивительно продуктивной полнофункциональной веб-инфраструктуры, построенной на основе таких отличных компонентов, как Hibernate, Spring Boot и Micronaut.

Статьи по данной тематике: