¿Cómo devolver una respuesta JSON compleja con Node.js?

82

Usando nodejs y express, me gustaría devolver uno o varios objetos (matriz) usando JSON. En el siguiente código, saco un objeto JSON a la vez. Funciona, pero esto no es exactamente lo que quiero. La respuesta producida no es una respuesta JSON válida ya que tengo muchos objetos.

Soy consciente de que simplemente podría agregar todos los objetos a una matriz y devolver esa matriz específica en res.end. Sin embargo, me temo que esto podría volverse pesado de procesar y requerir mucha memoria.

¿Cuál es la forma correcta de lograr esto con nodejs? ¿Es query.each el método correcto para llamar?

app.get('/users/:email/messages/unread', function(req, res, next) {
    var query = MessageInfo
        .find({ $and: [ { 'email': req.params.email }, { 'hasBeenRead': false } ] });

    res.writeHead(200, { 'Content-Type': 'application/json' });   
    query.each(function(err, msg) {
        if (msg) { 
            res.write(JSON.stringify({ msgId: msg.fileName }));
        } else {
            res.end();
        }
    });
});
Martín
fuente

Respuestas:

183

En express 3 puede usar directamente res.json ({foo: bar})

res.json({ msgId: msg.fileName })

Ver la documentación

zobi8225
fuente
9
como hacer eso sin expreso?
Piotrek
@ Ludwik11 res.write(JSON.stringify(foo)). Si fooes grande, es posible que deba cortarlo (en cadena, luego escribir trozos a la vez). Probablemente también quiera ver su encabezado "Content-Type":"application/json"o similar según corresponda.
OJFord
21

No sé si esto es realmente diferente, pero en lugar de iterar sobre el cursor de consulta, podría hacer algo como esto:

query.exec(function (err, results){
  if (err) res.writeHead(500, err.message)
  else if (!results.length) res.writeHead(404);
  else {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.write(JSON.stringify(results.map(function (msg){ return {msgId: msg.fileName}; })));
  }
  res.end();
});
danmactough
fuente
12

[Editar] Después de revisar la documentación de Mongoose, parece que puede enviar el resultado de cada consulta como un fragmento separado; el servidor web utiliza codificación de transferencia fragmentada de forma predeterminada lo que todo lo que tiene que hacer es envolver una matriz alrededor de los elementos para convertirlo en un objeto JSON válido.

Aproximadamente (no probado):

app.get('/users/:email/messages/unread', function(req, res, next) {
  var firstItem=true, query=MessageInfo.find(/*...*/);
  res.writeHead(200, {'Content-Type': 'application/json'});
  query.each(function(docs) {
    // Start the JSON array or separate the next element.
    res.write(firstItem ? (firstItem=false,'[') : ',');
    res.write(JSON.stringify({ msgId: msg.fileName }));
  });
  res.end(']'); // End the JSON array and response.
});

Alternativamente, como mencionas, simplemente puedes enviar el contenido de la matriz tal cual. En este caso, el cuerpo de la respuesta se almacenará en búfer y se enviará inmediatamente, lo que puede consumir una gran cantidad de memoria adicional (por encima de lo que se requiere para almacenar los resultados en sí) para conjuntos de resultados grandes. Por ejemplo:

// ...
var query = MessageInfo.find(/*...*/);
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(query.map(function(x){ return x.fileName })));
maerics
fuente
Esta es una buena idea. Sin embargo, me parece un poco extraño. Esperaba que nodejs proporcionara algo un poco más elegante.
Martin