Posso obter o JSON para carregar em um OrderedDict?

428

Ok, então eu posso usar um OrderedDict no json.dump. Ou seja, um OrderedDict pode ser usado como uma entrada para JSON.

Mas pode ser usado como saída? Se sim, como? No meu caso, gostaria de loadinserir um OrderedDict para manter a ordem das chaves no arquivo.

Caso contrário, existe algum tipo de solução alternativa?

c00kiemonster
fonte
Nunca tentei manter a ordem, embora eu certamente possa ver como isso seria útil.
feathj
1
Sim, no meu caso, estou fazendo uma ponte entre diferentes idiomas e aplicativos, e o JSON funciona muito bem. Mas a ordem das chaves é um pouco problemática. Seria incrível ter um simples json.loadregistro para usar OrderedDicts em vez de Dicts em Python.
C00kiemonster
3
JSON especificação tipo define objeto como tendo chaves desordenados ... esperando ordem da chave específica é um erro
Anentropic
3
A ordenação de chaves não costuma ser para qualquer tipo de requisitos funcionais. É principalmente apenas pela legibilidade humana. Se eu apenas quero que meu json seja bem impresso, não espero que nenhuma ordem do documento seja alterada.
Pickles
5
Também ajuda a evitar grandes diferenças de git!
Richard Rast

Respostas:

610

Sim você pode. Especificando o object_pairs_hookargumento para JSONDecoder . De fato, este é o exemplo exato fornecido na documentação.

>>> json.JSONDecoder(object_pairs_hook=collections.OrderedDict).decode('{"foo":1, "bar": 2}')
OrderedDict([('foo', 1), ('bar', 2)])
>>> 

Você pode passar esse parâmetro para json.loads(se você não precisar de uma instância de decodificador para outros fins) da seguinte forma:

>>> import json
>>> from collections import OrderedDict
>>> data = json.loads('{"foo":1, "bar": 2}', object_pairs_hook=OrderedDict)
>>> print json.dumps(data, indent=4)
{
    "foo": 1,
    "bar": 2
}
>>> 

O uso json.loadé feito da mesma maneira:

>>> data = json.load(open('config.json'), object_pairs_hook=OrderedDict)
SingleNegationElimination
fonte
3
Estou perplexo. Os documentos dizem que o object_pairs_hook é chamado para cada literal que é decodificado em pares. Por que isso não cria um novo OrderedDict para cada registro no JSON?
Tim Keating
3
Hmm ... os documentos são redigidos de forma ambígua. O que eles querem dizer como que o "resultado inteiro de decodificação de todos os pares" será passada, em ordem, como uma lista, para object_pairs_hook, em vez de "cada par será passado para object_pairs_hook,"
SingleNegationElimination
Mas perde a ordem original da entrada json?
SIslam
Ficou surpreso ao ver que json.loadnão mantê-lo ordenados por padrão, mas parece que é apenas um reflexo do que json em si faz - o {}são não-ordenada, mas o []no json são ordenados conforme descrito aqui
cardamomo
1
@RandomCertainty sim, toda vez que um objeto JSON for encontrado ao analisar a fonte, OrderedDictserá usado para criar o valor python resultante.
SingleNegationElimination
125

Versão simples para Python 2.7+

my_ordered_dict = json.loads(json_str, object_pairs_hook=collections.OrderedDict)

Ou para Python 2.4 a 2.6

import simplejson as json
import ordereddict

my_ordered_dict = json.loads(json_str, object_pairs_hook=ordereddict.OrderedDict)
mjhm
fonte
4
Ahhh, mas não inclui o object_pairs_hook - e é por isso que você ainda precisa do simplejson na 2.6. ;)
mjhm
8
Deseja observar isso simplejsone ordereddictsão bibliotecas separadas que você precisa instalar.
phunehehe
2
para python 2.7+: "import json, collections" no código, para python2.6- "aptitude install python-pip" e "pip install ordersdict" no sistema
ZiTAL
Isso é muito mais fácil e rápido do que o método anterior com JSONDecoder.
Natim
Estranhamente, no pypy, o json incluído falhará loads('{}', object_pairs_hook=OrderedDict).
Matthew Schinckel
37

Boas notícias! Desde a versão 3.6, a implementação do cPython preservou a ordem de inserção dos dicionários ( https://mail.python.org/pipermail/python-dev/2016-September/146327.html ). Isso significa que a biblioteca json agora está preservando a ordem por padrão. Observe a diferença de comportamento entre o python 3.5 e 3.6. O código:

import json
data = json.loads('{"foo":1, "bar":2, "fiddle":{"bar":2, "foo":1}}')
print(json.dumps(data, indent=4))

No py3.5, a ordem resultante é indefinida:

{
    "fiddle": {
        "bar": 2,
        "foo": 1
    },
    "bar": 2,
    "foo": 1
}

Na implementação cPython do python 3.6:

{
    "foo": 1,
    "bar": 2,
    "fiddle": {
        "bar": 2,
        "foo": 1
    }
}

A boa notícia é que isso se tornou uma especificação de linguagem a partir do python 3.7 (em oposição a um detalhe de implementação do cPython 3.6+): https://mail.python.org/pipermail/python-dev/2017-December/151283 .html

Portanto, a resposta para sua pergunta agora é: atualize para python 3.6! :)

Pelson
fonte
1
Embora eu veja o mesmo comportamento que você no exemplo dado, na implementação CPython do Python 3.6.4, isso json.loads('{"2": 2, "1": 1}')se torna {'1': 1, '2': 2}para mim.
Fuglede
1
@fuglede parece com as dict.__repr__chaves de classificação enquanto a ordem subjacente é preservada. Em outras palavras, json.loads('{"2": 2, "1": 1}').items()é dict_items([('2', 2), ('1', 1)])mesmo se repr(json.loads('{"2": 2, "1": 1}'))é "{'1': 1, '2': 2}".
Simon Charette
@ SimonCharette Hm, poderia ser; Na verdade, sou incapaz de reproduzir minha própria observação nos pacotes da conda / main / win-64 :: python-3.6.4-h0c2934d_3, por isso será difícil de testar.
fuglede 9/02/19
Isso realmente não ajuda muito, pois a "renomeação" de chaves ainda arruinará a ordem das chaves.
Hubro 30/01
7

Você sempre pode escrever a lista de chaves, além de despejar o dict e, em seguida, reconstruí-lo OrderedDictiterando pela lista?

Âmbar
fonte
1
+1 para solução de baixa tecnologia. Eu fiz isso ao lidar com o mesmo problema com o YAML, mas ter que duplicar é meio coxo, especialmente quando o formato subjacente preserva a ordem. Também pode fazer sentido evitar perder pares de valores-chave que estão no ditado, mas que estão ausentes da lista de chaves, aderindo-os depois de todos os itens ordenados explicitamente.
Mu mente
2
A solução de baixa tecnologia também preserva o contexto que não é necessariamente preservado no formato exportado (IOW; alguém vê o JSON e não há nada lá declarando explicitamente "essas chaves devem permanecer nessa ordem" se fizerem manipulações).
2222 Amber
O que determina que a lista de chaves "despejadas" está na ordem correta? E os ditados aninhados? Parece que tanto o dumping precisaria lidar com isso e a reconstrução precisaria ser feita recursivamente usando OrdereDicts.
martineau
5

Além de descartar a lista ordenada de chaves ao lado do dicionário, outra solução de baixa tecnologia, que tem a vantagem de ser explícita, é despejar a lista (ordenada) de pares de valores-chave ordered_dict.items(); o carregamento é simples OrderedDict(<list of key-value pairs>). Isso lida com um dicionário ordenado, apesar do JSON não ter esse conceito (os dicionários JSON não têm ordem).

É realmente bom aproveitar o fato de que jsondespeja o OrderedDict na ordem correta. No entanto, em geral é desnecessariamente pesado e não necessariamente significativo ter que ler todos os dicionários JSON como um OrderedDict (por meio do object_pairs_hookargumento); portanto, uma conversão explícita apenas dos dicionários que devem ser ordenados também faz sentido.

Eric O Lebigot
fonte
4

O comando de carregamento normalmente usado funcionará se você especificar o parâmetro object_pairs_hook :

import json
from  collections import OrderedDict
with open('foo.json', 'r') as fp:
    metrics_types = json.load(fp, object_pairs_hook=OrderedDict)
ntg
fonte