Разбор файлов конфигурации 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.