Одновременное чтение и сопоставление нескольких огромных файлов JSON / NDJSON в JavaScript - PullRequest
1 голос
/ 07 июня 2019

Существует два файла, упорядоченные по id, parent_data.json и child_data.json. Дети являются подмножеством родителей.

Я пытаюсь сопоставить parent.id с child.id. Я пытаюсь сделать один проход на файл .json. Вместо того, чтобы делать что-то наподобие вложенного forEach / map, которое проходит через каждого потомка.

Предположим, что оба файла .json содержат несколько гигов / миллионов строк, перечитывание children.json займет много времени с несколькими проходами, и загрузка всего в память также не может быть и речи.

Каждый файл может иметь формат JSON или NDJSON . Текущий пример - NDJSON.

parent_data.json:

{"id":1, "name": "parent_1"}
{"id":2, "name": "parent_2"}
{"id":3, "name": "parent_3"}
{"id":4, "name": "parent_4"}
{"id":5, "name": "parent_5"}
{"id":6, "name": "parent_6"}
{"id":7, "name": "parent_7"}

child_data.json:

{"id":4, "name":"belongs_to_parent_4", "guid": "${unique_id}"}
{"id":4, "name":"belongs_to_parent_4", "guid": "${unique_id}"}
{"id":5, "name":"belongs_to_parent_5", "guid": "${unique_id}"}
{"id":7, "name":"belongs_to_parent_7", "guid": "${unique_id}"}
{"id":7, "name":"belongs_to_parent_7", "guid": "${unique_id}"}

Ожидаемый результат:

[
    {
        "id": 4,
        "name": "parent_4",
        "children": [
            {
                "id": 4,
                "name": "belongs_to_parent_4",
                "guid": "${unique_id}"
            },
            {
                "id": 4,
                "name": "belongs_to_parent_4",
                "guid": "${unique_id}"
            }
        ]
    },
    {
        "id": 5,
        "name": "parent_5",
        "children": [
            {
                "id": 5,
                "name": "belongs_to_parent_5",
                "guid": "${unique_id}"
            }
        ]
    },
    {
        "id": 7,
        "name": "parent_7",
        "children": [
            {
                "id": 7,
                "name": "belongs_to_parent_7",
                "guid": "${unique_id}"
            }
        ]
    }
]

Я не могу найти достойный способ перебрать любой из потоков, не пропустив несколько дочерних элементов в конечном результате.

Этот код пропускает двух первых потомков каждого из родителей:

const fs = require('fs');
const es = require('event-stream');

const main = () => {
    let currentParent = null;
    let currentChild = null;
    let prevChild = null;

    let parentLines = 0;
    let childLines = 0;

    let tmpChildren = [];
    const parentStream = fs
        .createReadStream('parent_data.json') // Parent Stream
        .pipe(es.split('\n')) // Delimit by \n
        .pipe(
            es.mapSync(line => { // Current line, without the delimiter \n
                parentStream.pause(); // Pause stream until done processing this line
                ++parentLines; // Debug
                currentParent = JSON.parse(line); // Now it's valid JSON object
                console.log('currentParent', currentParent.id); // Debug
            }),
        )
        .on('error', e => {
            console.error(e);
        })
        .on('close', () => {
            console.log('close reading parent stream');
        })
        .on('end', () => {
            console.log('end reading parent stream');
        });

    const childStream = fs
        .createReadStream('child_data.json') // Child Stream
        .pipe(es.split('\n')) // Split by delimiter
        .pipe(
            es.mapSync(line => { // Current child line, without delimiter
                ++childLines; // Debug
                childStream.pause(); // Pause child stream
                currentChild = JSON.parse(line); // Valid JSON child now
                if (prevChild && (currentParent.id === prevChild.id)) { // Check prevChild and currentParent
                    tmpChildren.push(prevChild);
                    prevChild = null;
                }
                if (currentChild && (currentParent.id == currentChild.id)) { // Check currentChild and currentParent
                    console.log('child', currentChild.id);
                    tmpChildren.push(currentChild);
                    // childStream.resume(); // Having this here will cause the stream to stop processing entirely
                } else {
                    // We're here because currentParent does not match currentChild, move to next parent
                    prevChild = currentChild;
                    if (tmpChildren.length > 0) {
                        currentParent['children'] = tmpChildren;
                        tmpChildren = [];
                    }
                    parentStream.resume();
                }
                console.log('currentChild', currentChild.id);
                childStream.resume(); // Having this here will cause the stream to skip children on new parent
            }),
        )
        .on('error', e => {
            console.error(e);
        })
        .on('close', () => {
            console.log('close reading child stream');
        })
        .on('end', () => {
            console.log('end reading child stream');
        });
};

main();

TLDR: Соответствует parent.id для child.id, если нет совпадений, сохраните child. Перейдите к следующему родителю, проверьте для prevChild.id и / или child.id на parent.id. Если совпадение добавит prevChild / child к parent [children], выполните итерацию child. Если совпадений нет, переходите к следующему родителю ... и т.д.

Может быть, графика поможет

...