Me gustaría leer varios objetos JSON de un archivo / flujo en Python, uno a la vez. Desafortunadamente, json.load()
solo .read()
s hasta el final del archivo; no parece haber ninguna forma de usarlo para leer un solo objeto o para iterar perezosamente sobre los objetos.
¿Hay alguna forma de hacer esto? Usar la biblioteca estándar sería ideal, pero si hay una biblioteca de terceros, la usaría en su lugar.
Por el momento, estoy poniendo cada objeto en una línea separada y lo estoy usando json.loads(f.readline())
, pero realmente preferiría no tener que hacer esto.
Ejemplo de uso
ejemplo.py
import my_json as json
import sys
for o in json.iterload(sys.stdin):
print("Working on a", type(o))
in.txt
{"foo": ["bar", "baz"]} 1 2 [] 4 5 6
sesión de ejemplo
$ python3.2 example.py < in.txt
Working on a dict
Working on a int
Working on a int
Working on a list
Working on a int
Working on a int
Working on a int
python
json
serialization
Jeremy
fuente
fuente
{"foo": ["bar", "baz"]}
en mi ejemplo), deberíayield
hacerlo y luego continuar con el siguiente (1
).'\n'
(una sola línea nueva, no dos caracteres) en su representación json porque'\n'
debe escaparse dentro de una cadena json y, por'\n'
lo tanto, puede usarse para formatear solo, por ejemplo, creo quejson.dumps()
no ' t introducir'\n'
por defecto. Tenga en cuenta que las nuevas líneas Unicode, como U + 0085, pueden no tener escape dentro de las cadenas json.Respuestas:
Aquí tienes una solución mucho más sencilla. El secreto es probar, fallar y usar la información de la excepción para analizar correctamente. La única limitación es que el archivo debe poder buscarse.
Editar: acabo de notar que esto solo funcionará para Python> = 3.5. Para las anteriores, las fallas devuelven un ValueError, y debe analizar la posición de la cadena, por ejemplo
fuente
re
no funcionará, las barras invertidas deben escapar. Considere una cuerda crudar'...'
.ujson
lugar dejson
usted, obtendrá una gran aceleraciónJSON generalmente no es muy bueno para este tipo de uso incremental; no existe una forma estándar de serializar varios objetos para que puedan cargarse fácilmente uno a la vez, sin analizar todo el lote.
El objeto por solución de línea que está utilizando también se ve en otra parte. Scrapy lo llama 'líneas JSON':
Puedes hacerlo un poco más Pythonically:
Creo que esta es la mejor manera: no depende de bibliotecas de terceros y es fácil entender lo que está sucediendo. También lo he usado en algunos de mis propios códigos.
fuente
Quizás un poco tarde, pero tuve este problema exacto (bueno, más o menos). Mi solución estándar para estos problemas suele ser simplemente hacer una división de expresiones regulares en algún objeto raíz conocido, pero en mi caso fue imposible. La única forma factible de hacer esto de forma genérica es implementar un tokenizador adecuado .
Después de no encontrar una solución suficientemente genérica y con un rendimiento razonable, terminé haciendo esto yo mismo, escribiendo el
splitstream
módulo. Es un pre-tokenizador que comprende JSON y XML y divide un flujo continuo en varios fragmentos para analizar (aunque deja el análisis real en sus manos). Para obtener algún tipo de rendimiento, está escrito como un módulo C.Ejemplo:
fuente
Seguro que puedes hacer esto. Solo tienes que ir
raw_decode
directamente. Esta implementación carga todo el archivo en la memoria y opera en esa cadena (tanto como lojson.load
hace); si tiene archivos grandes, puede modificarlo para que solo lea del archivo según sea necesario sin mucha dificultad.Uso: tal como lo solicitó, es un generador.
fuente
Este es un problema bastante desagradable en realidad porque tiene que transmitir en líneas, pero el patrón coincide en varias líneas contra llaves, pero también el patrón coincide con json. Es una especie de json-preparse seguido de un json parse. Json es, en comparación con otros formatos, fácil de analizar, por lo que no siempre es necesario buscar una biblioteca de análisis, sin embargo, ¿cómo deberíamos resolver estos problemas en conflicto?
¡Generadores al rescate!
La belleza de los generadores para un problema como este es que puede apilarlos uno encima del otro, abstrayendo gradualmente la dificultad del problema mientras mantiene la pereza. También consideré usar el mecanismo para devolver valores a un generador (send ()) pero, afortunadamente, descubrí que no necesitaba usarlo.
Para resolver el primero de los problemas, necesita algún tipo de streamfinditer, como una versión de streaming de re.finditer. Mi intento en esto a continuación extrae líneas según sea necesario (descomente la declaración de depuración para ver) mientras aún devuelve coincidencias. De hecho, lo modifiqué ligeramente para producir líneas no coincidentes y coincidencias (marcadas como 0 o 1 en la primera parte de la tupla producida).
Con eso, entonces es posible hacer coincidir los frenos, tener en cuenta cada vez si los frenos están equilibrados y luego devolver objetos simples o compuestos según corresponda.
Esto devuelve tuplas de la siguiente manera:
Básicamente, esa es la parte desagradable hecha. Ahora solo tenemos que hacer el nivel final de análisis como mejor nos parezca. Por ejemplo, podemos usar la función iterload de Jeremy Roman (¡Gracias!) Para analizar una sola línea:
Pruébalo:
Obtengo estos resultados (y si enciende esa línea de depuración, verá que tira de las líneas según sea necesario):
Esto no funcionará en todas las situaciones. Debido a la implementación de la
json
biblioteca, es imposible trabajar completamente correctamente sin volver a implementar el analizador usted mismo.fuente
"}"
y"]"
aparecen dentro de cadenas JSON? Creo que esta es una limitación general del análisis con expresiones regulares.Creo que una mejor forma de hacerlo sería utilizar una máquina de estado. A continuación se muestra un código de muestra que resolví convirtiendo un código NodeJS en el enlace de abajo a Python
3 (se usó una palabra clave no local solo disponible en Python 3, el código no funcionará en Python 2)Edit-1: código actualizado y compatible con Python 2
Edit-2: actualizado y agregado una versión solo de Python3 también
https://gist.github.com/creationix/5992451
Versión única de Python 3
Versión compatible con Python 2
Probándolo
La salida de la misma es
fuente
Me gustaría dar una solución. El pensamiento clave es "intentar" decodificar: si falla, dele más alimentación; de lo contrario, utilice la información de compensación para preparar la siguiente decodificación.
Sin embargo, el módulo json actual no puede tolerar el ESPACIO en la cabeza de la cadena para decodificar, así que tengo que quitarlos.
========================= He probado varios archivos txt y funciona bien. (in1.txt)
(in2.txt)
(en.txt, su inicial)
(salida para el caso de prueba de Benedict)
fuente
Aquí está el mío:
fuente
Usé la elegante solución de @ wuilang. El enfoque simple - leer un byte, intentar decodificar, leer un byte, intentar decodificar, ... - funcionó, pero desafortunadamente fue muy lento.
En mi caso, estaba intentando leer objetos JSON "bastante impresos" del mismo tipo de objeto desde un archivo. Esto me permitió optimizar el enfoque; Pude leer el archivo línea por línea, solo decodificando cuando encontré una línea que contenía exactamente "}":
Si trabaja con JSON compacto de una por línea que escapa de las líneas nuevas en los literales de cadena, puede simplificar aún más este enfoque de manera segura:
Obviamente, estos enfoques simples solo funcionan para tipos muy específicos de JSON. Sin embargo, si se cumplen estas suposiciones, estas soluciones funcionan correcta y rápidamente.
fuente
Si usa una instancia json.JSONDecoder, puede usar la
raw_decode
función miembro. Devuelve una tupla de representación de Python del valor JSON y un índice donde se detuvo el análisis. Esto facilita la división (o la búsqueda en un objeto de flujo) de los valores JSON restantes. No estoy tan contento con el ciclo while adicional para omitir el espacio en blanco entre los diferentes valores JSON en la entrada, pero en mi opinión, hace el trabajo.La siguiente versión es mucho más corta y se come la parte de la cadena que ya está analizada. Parece que, por alguna razón, una segunda llamada json.JSONDecoder.raw_decode () parece fallar cuando el primer carácter de la cadena es un espacio en blanco, esa es también la razón por la que omito el espacio en blanco en el whileloop anterior ...
En la documentación sobre la clase json.JSONDecoder, el método raw_decode https://docs.python.org/3/library/json.html#encoders-and-decoders contiene lo siguiente:
Y estos datos extraños pueden ser fácilmente otro valor JSON. En otras palabras, el método podría estar escrito con este propósito en mente.
Con input.txt usando la función superior, obtengo el resultado de ejemplo como se presenta en la pregunta original.
fuente
Puede usar https://pypi.org/project/json-stream-parser/ exactamente para ese propósito.
salida
fuente