Estou acompanhando a configuração de algum sistema no formato JSON em um sistema de controle de revisão.
Infelizmente, essa configuração é recuperada usando algum comando proprietário de código fechado, e a saída muda de uma execução para a próxima, pois a ordem nos objetos e matrizes é mais ou menos aleatória.
Uma vez, ele produzirá:
{
"fru": [
{
"name": "foo",
"attr": [
{"name": "colour", "value": "blue"},
{"name": "length", "value": 12}
]
},
{
"name": "bar",
"attr": [
{"name": "colour", "value": "red"},
{"name": "length", "value": 1}
]
}
],
"tags": ["x", "y"]
}
E da próxima vez:
{
"tags": ["y", "x"],
"fru": [
{
"name": "bar",
"attr": [
{"name": "length", "value": 1},
{"name": "colour", "value": "red"}
]
},
{
"name": "foo",
"attr": [
{"name": "colour", "value": "blue"},
{"name": "length", "value": 12}
]
}
]
}
Isso significa que, do ponto de vista de git diff
, tudo muda de uma execução para outra, mesmo que seja exatamente o mesmo sistema.
Em todas as matrizes, a ordem não é relevante. A ordem também não importa nos atributos dos objetos. Então, se eu pudesse pós-processar essa saída para que os objetos e arrays tivessem seus atributos e membros em uma ordem consistente, eu garantiria que a saída não mudaria quando o sistema não mudasse e as mudanças vistas por git diff
seriam mais provavelmente refletirá as mudanças no sistema.
jq -S
me ajuda bastante:
- classificando atributos dentro de objetos
- colocar atributos de objetos separados e membros da matriz em linhas separadas (
git diff
é baseado em linhas).
Para o exemplo acima, isso me dá:
{
"fru": [
{
"attr": [
{
"name": "colour",
"value": "blue"
},
{
"name": "length",
"value": 12
}
],
"name": "foo"
},
{
"attr": [
{
"name": "colour",
"value": "red"
},
{
"name": "length",
"value": 1
}
],
"name": "bar"
}
],
"tags": [
"x",
"y"
]
}
E:
{
"fru": [
{
"attr": [
{
"name": "length",
"value": 1
},
{
"name": "colour",
"value": "red"
}
],
"name": "bar"
},
{
"attr": [
{
"name": "colour",
"value": "blue"
},
{
"name": "length",
"value": 12
}
],
"name": "foo"
}
],
"tags": [
"y",
"x"
]
}
Isso é melhor, mas ainda não está lá, pois os arrays não estão classificados (compreensivelmente).
Observe que o arquivo da vida real é mais complexo com arrays contendo outros arrays de objetos contendo mais arrays...
Meu pensamento para resolver isso é classificar todas as matrizes, começando pelas mais profundas com base na representação da string JSON dos valores, por exemplo, .fru[0].attr
classificada com {"name": "colour", "value": "blue"}
antes {"name": "length", "value": 12}
porque a {"name":"colour","value":"blue"}
string é classificada antes do comprimento um e, em seguida, a .fru
matriz classificada com foo
antes bar
porque {"attr":[..."blue"...
(com o attr
atributo movido antes name
em ordem alfabética) é classificado antes {"attr":[..."red"...
.
Posso obter os caminhos de todos os arrays, primeiro a profundidade com:
$ jq -c '[paths(arrays)]|reverse' a
[["tags"],["fru",1,"attr"],["fru",0,"attr"],["fru"]]
Posso classificar uma matriz com base na representação da string JSON de seus membros com:
jq '.array|=sort_by(tojson)'
Mas como combinar os dois para aplicar o segundo a todos os arrays retornados pelo primeiro?
Ou existe uma maneira melhor de pós-processar esse JSON para que o pedido permaneça consistente?
Se jq
não for a melhor ferramenta para isso, ficarei feliz em considerar os módulos perl
do JSON
ou os equivalentes Ruby/python.
A
walk()
função parece perfeita para este caso de uso. Ele aplica recursivamente o filtro fornecido a cada elemento JSON, de baixo para cima, e retorna o resultado. Na verdade, classificar todos os arrays é um dos exemplos da documentação :Se tudo que você precisa é de uma ordem de classificação consistente, isso deve resolver (já que
sort
o filtro do jq define uma ordem determinística de todos os elementos, incluindo objetos), mas se você deseja classificar especificamente os elementos da matriz por suas representações de string, você pode, é claro substituasort
pelo seusort_by(tojson)
.Aliás, você pode achar útil emparelhar isso com uma ferramenta de comparação JSON que pode comparar estruturalmente dois documentos JSON (por exemplo, ignorando a ordem das chaves nos objetos), em vez de fazer uma comparação baseada em texto, mas isso é outro assunto.