首页 > 解决方案 > Javascript 如何正确获取嵌套对象

问题描述

我正在构建一个 Electron 应用程序,它从用户那里获取某种 json 文件并记录其中的所有数据。但是我收到关于从数量中获取未定义的错误:Json File:

{
  "tiers": [
    {
      "trades": [
        {
          "wants": [
            {
              "item": "minecraft:string",
              "quantity": {
                "min": 15,
                "max": 20
              }
            }
          ],
          "gives": [
            {
              "item": "minecraft:emerald"
            }
          ]
        },
        {
          "wants": [
            {
              "item": "minecraft:emerald"
            }
          ],
          "gives": [
            {
              "item": "minecraft:arrow",
              "quantity": {
                "min": 8,
                "max": 12
              }
            }
          ]
        }
      ]
    },
    {
      "trades": [
        {
          "wants": [
            {
              "item": "minecraft:gravel",
              "quantity": 10
            },
            {
              "item": "minecraft:emerald",
              "quantity": 1
            }
          ],
          "gives": [
            {
              "item": "minecraft:flint",
              "quantity": {
                  "min": 6,
                  "max": 10
                }
            }
          ]
        },
        {
          "wants": [
            {
              "item": "minecraft:emerald",
              "quantity": {
                "min": 2,
                "max": 3
              }
            }
          ],
          "gives": [
            {
              "item": "minecraft:bow"
            }
          ]
        }
      ]
    }
  ]
}

这是 main.js:

const { app, BrowserWindow, ipcMain, dialog, ipcRenderer, globalShortcut } = require('electron');
const { autoUpdater } = require('electron-updater');
const path = require('path');
const Store = require('./classes/Store.js');
const { electron } = require('process');
const { setTimeout } = require('timers');

// import Vue from 'vue';
// import Vuetify from 'vuetify';
// import "vuetify/dist/vuetify.min.css";

// Vue.use(Vuetify);

let win;
let loadingScreen;

const store = new Store({
    configName: 'settings',
    defaults: {
        windowBounds: {
            width: 800,
            height: 600,
            x: 0,
            y: 0,
        },
        isMaximized: false,
        fullscreen: false
    }
});

function createLoadingScreen() {
    loadingScreen = new BrowserWindow(Object.assign({
        width: 200,
        height: 220,
        frame: false,
        transparent: true,
        icon: 'build/icons/icon.png',
        webPreferences: {
            worldSafeExecuteJavaScript: true
        }
    }));
    loadingScreen.setResizable(false);
    loadingScreen.loadURL('file://' + __dirname + '/extraWindows/loadingScreen/loading.html');
    loadingScreen.setOverlayIcon('build/icons/icon.png', "Route");
    loadingScreen.on('closed', () => loadingScreen = null);
    loadingScreen.webContents.on('did-finish-load', () => {
        loadingScreen.show();
    });
}

function createWindow() {
    let width = store.get('windowBounds.width');
    let height = store.get('windowBounds.height');
    let x = store.get('windowBounds.x');
    let y = store.get('windowBounds.y');
    let isMaximized = store.get('isMaximized');

    win = new BrowserWindow({
        width,
        height,
        x,
        y,
        icon: 'build/icons/icon.png',
        frame: false,
        titleBarStyle: "hidden",
        webPreferences: {
            enableRemoteModule: true,
            nodeIntegration: true,
            webSafeExecuteJavaScript: true
        },
        show: false
    })

    win.setOverlayIcon('build/icons/icon.png', "Route");

    if(isMaximized == true) {
        win.maximize();
    }

    win.loadFile('index.html');

    win.on('closed', function() {
        win = null;
        settingScreen = null;
    });
    win.webContents.on('did-finish-load', () => {
        if(loadingScreen) {
            loadingScreen.close();
            win.show();
        }
    })

    win.on('resize', () => {
        store.set('windowBounds', win.getBounds());
    });
    win.on('move', () => {
        store.set('windowBounds', win.getBounds());
    })
    win.on('maximize', () => {
        store.set('isMaximized', true);
    })
    win.on('unmaximize', () => {
        store.set('isMaximized', false);
    })
    win.on('show', () => {
        win.setFullScreen(store.get('fullscreen'));
    })
    win.once('ready-to-show', () => {
        autoUpdater.checkForUpdatesAndNotify();
    })
}

function createSettings() {
    settingScreen = new BrowserWindow({
        width: 600,
        height: 800,
        frame: false,
        parent: win,
        modal: true,
        titleBarStyle: "hidden",
        webPreferences: {
            enableRemoteModule: true,
            nodeIntegration: true,
            worldSafeExecuteJavaScript: true
        },
        show: false
    })

    settingScreen.loadFile('extraWindows/settingsScreen/settings.html');

    settingScreen.on('close', (event) => {
        event.preventDefault();
        settingScreen.hide();
    })
}


app.on('ready', () => {
    createLoadingScreen();

    setTimeout(() => {
        createWindow();
        createSettings();
    }, 5000); // Set to 5000
});

app.on('window-all-closed', () => {
    if(process.platform !== 'darwin') {
        app.quit()
    }
})

app.on('activate', () => {
    if(BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
})

app.whenReady().then(() => {
    globalShortcut.register('CmdOrCtrl+O', () => {
        win.webContents.send('openFile');
    })
    globalShortcut.register('CmdOrCtrl+D', () => {
        win.webContents.send('closeFile');
    })
})

// These are all of the ipc functions needed for Route to work

ipcMain.handle('viewSettings', (event, arg) => {
    if(arg === "true") {
        settingScreen.show();
    } else if(arg === "false") {
        settingScreen.hide();
    }
});

ipcMain.handle('changeSettings', (event, arg) => {
    if(arg === "fullScreen") {
        if(win.isFullScreen() == false) {
            win.setFullScreen(true);
            store.set('fullscreen', true);
        } else {
            win.setFullScreen(false);
            store.set('fullscreen', false);
        }
        // Fill in the data for fullscreen
    } else if(arg === 'themechooser') {
        // Fill in the data for themes
    }
});

ipcMain.on('appVersion', (event) => {
    event.sender.send('appVersion', { version: app.getVersion() });
});

ipcMain.on('restartApp', () => {
    autoUpdater.quitAndInstall();
})

autoUpdater.on('updateAvailable', () => {
    win.webContents.send('updateAvailable');
});

autoUpdater.on('updateDownloaded', () => {
    win.webContents.send('updateDownloaded');
})

这是 renderer.js:

const { app, ipcRenderer } = require('electron');
const Store = require('./classes/Store.js');
const remote = require('electron').remote;
const dialog = require('electron').remote.dialog;
const fs = require('fs');
const { S_IFDIR } = require('constants');
const { electron } = require('process');
const { version } = require('os');

let currentWindow = remote.getCurrentWindow();
let $ = function(selector) {
    return document.querySelector(selector);
}

let projectArray = [];
// There can only be 5 projects max. Might add more.
let projectCount = 0;

document.querySelector('#tradeSetup').addEventListener('click', () => {
    //Create a new setup.
    alert("This works!");
});

let fileOpenerOptions = {
    title: "Open Trade File",
    defaultPath: "C:\\",
    buttonLabel: "Start Coding",
    properties: [ 'openFile' ],
    filters: [
        { name: 'Json', extensions: ['json'] },
        { name: 'All FIles', extensions: ['*'] }
    ]
}

//Top bar buttons
function openFile() {
    dialog.showOpenDialog(currentWindow, fileOpenerOptions).then(fileNames => {
        if(fileNames == undefined || fileNames == null) {
            console.log("No file selected.");
            return;
        } else {
            let projectName = path.basename(fileNames.filePaths[0], path.extname(fileNames.filePaths[0]));
            let rawFileData = fs.readFileSync(fileNames.filePaths[0]);
            let fileData = JSON.parse(rawFileData);
            console.log(fileData);
            if(projectCount < 5) {
                projectCount++;
                createProject(projectName, fileData);
                // Run code
            }  else if (projectCount == 5) {
                alert('Support for more projects is not available yet!\nPlease wait until a later update to have more than 5 projects.');
            }
        }
    });
}

function openSettings() {
    ipcRenderer.invoke('viewSettings', "true");
}

function closeFile() {
    if(document.querySelector('.activeProjectButton') == null) {
        console.log("No project to close.");
    } else {
        let contine = confirm("Is this project the furthest project to the right? If not, please don't close it.\nThis is a bug that is being worked on.");
        if(contine == true) {
            document.querySelector('.activeProjectButton').remove();
            document.querySelector('.activeWorkspace').remove();
            projectCount--;
            document.querySelectorAll('.inactiveProjectWorkspace').j--;
        }
        // Add a fun little secret for users
    }
}
 
document.querySelector('#openFile').addEventListener('click', () => {
    openFile();
});

document.querySelector('#openSettings').addEventListener('click', () => {
    openSettings();
});

document.querySelector('#closeFile').addEventListener('click', () => {
    closeFile();
})

// Create and switch projects
function createProject(name, projectData) {
    // This is a beta feature for now. It is not finished
    // Create the tab
    let projectInProduction = document.createElement("button");
    projectInProduction.id = "project" + projectCount + "Click";
    projectInProduction.classList.add("invisButton");
    projectInProduction.classList.add("inactiveProjectButton");
    projectInProduction.classList.add("project" + projectCount);
    projectInProduction.innerHTML += (name);

    // Remove the welcome message
    document.querySelector('.unHidden').classList.add('hidden');
    document.querySelector('.unHidden').classList.remove('unHidden');

    //Create the workspace
    let workspaceInProduction = document.createElement("div");
    workspaceInProduction.classList.add("inactiveWorkspace");
    projectArray.push(projectData);
    if(projectArray[projectCount - 1].tiers == null) {
        alert("This is not a trade file!\nPlease use a trade file!");
    } else {
        $('.projects').appendChild(projectInProduction);
        $('#jsonProjectContainer').appendChild(workspaceInProduction);
        let table = document.createElement('table');
        table.innerHTML += '<tr><th colspan="3">Buying</th><th colspan="2">Selling</th></tr>';
        table.classList.add('table' + projectCount);
        workspaceInProduction.appendChild(table);
        let tiers = Object.keys(projectArray[projectCount - 1].tiers).length;
        console.log(tiers);
        for(let i = 0; i < tiers; i++) {
            let tradesC = Object.keys(projectArray[projectCount - 1].tiers[i].trades).length;
            for(let j = 0; j < tradesC; j++) {
                let wants = Object.keys(projectArray[projectCount - 1].tiers[i].trades[j].wants);
                let wantsC = Object.keys(projectArray[projectCount - 1].tiers[i].trades[j].wants).length;
                let gives = Object.keys(projectArray[projectCount - 1].tiers[i].trades[j].gives);
                for(let k = 0; k < wantsC; k++) {
                    let wantsItem = projectArray[projectCount - 1].tiers[i].trades[j].wants[k].item;
                    let wantsMin = projectArray[projectCount - 1].tiers[i].trades[j].wants[k].quantity["min"];
                    let wantsMax = projectArray[projectCount - 1].tiers[i].trades[j].wants[k].quantity["max"];
                    let givesItem = projectArray[projectCount - 1].tiers[i].trades[j].gives[0].item;
                    console.log("Wants: " + wants[k]);
                    console.log("Min: " + wantsMin);
                    console.log("Max: " + wantsMax);
                    console.log("Item: " + wantsItem);
                    console.log("Gives: " + gives);
                    console.log("Item: " + givesItem);
                }
            }
        }

    }
    // Add a onlclick function to make switching projects work
    j = projectCount;
    document.getElementById('project' + projectCount + 'Click').addEventListener('click', function(j) {
        let buttonIndex = j;
        let buttonNodes = $('.projects').children;
        let workspaceNodes = $('#jsonProjectContainer').children;
        for(let i = 0; i < buttonNodes.length; i++) {
            if(i == (j - 1)) {
                buttonNodes[i].classList.add('activeProjectButton');
                buttonNodes[i].classList.remove('inactiveProjectButton');
                workspaceNodes[i].classList.add('activeWorkspace');
                workspaceNodes[i].classList.remove('inactiveWorkspace');
            } else {
                buttonNodes[i].classList.remove('activeProjectButton');
                buttonNodes[i].classList.add('inactiveProjectButton');
                workspaceNodes[i].classList.remove('activeWorkspace');
                workspaceNodes[i].classList.add('inactiveWorkspace');
            }
        }
    }.bind(null, j));
}

// Title bar scripts
ipcRenderer.on('openFile', () => {
    openFile();
})
ipcRenderer.on('closeFile', () => {
    closeFile();
})

// App updates
ipcRenderer.send('appVersion');
ipcRenderer.on('appVersion', (event, arg) => {
    ipcRenderer.removeAllListeners('appVersion');
    //version.innerText = ;
});

const updateNotifier = document.getElementById('updateNotifier');
const updateAvailability = document.getElementById('updateAvailability');
const restartButton = document.getElementById('restartButton');

ipcRenderer.on('updateAvailable', () => {
    ipcRenderer.removeAllListeners('updateAvailable');
    updateAvailability.innerText = 'A new version of Route is available. Downloading now...';
    updateNotifier.classList.remove('hidden');
});

ipcRenderer.on('update_downloaded', () => {
    ipcRenderer.removeAllListeners('updateDownloaded');
    updateAvailability.innerText = 'Update downloaded. It will be installed on restart. Restart to continue.';
    restartButton.classList.remove('hidden');
    updateNotifier.classList.remove('hidden');
});
function restartApp() {
    ipcRenderer.send('restartApp');
}

这是 index.html:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>route - Minecraft Addon Tool</title>
        <!-- https://electronjs.org/docs/tutorial/security#csp-meta-tag -->
        <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
        <link href="index.css" type="text/css" rel="stylesheet" />
        <link href="node_modules/@mdi/font/css/materialdesignicons.min.css" type="text/css" rel="stylesheet" />
    </head>
    <body>
        <script>
            // const electron = require('electron').remote;
            const path = require('path');
            const customTitleBar = require('custom-electron-titlebar');
            const Menu = require('electron').remote.Menu;
            const MenuItem = require('electron').remote.MenuItem;

            let mainTitlebar = new customTitleBar.Titlebar({
                backgroundColor: customTitleBar.Color.fromHex('#391B47'),
                icon: 'build/icons/icon.png'
            });

            const menu = new Menu();
            menu.append(new MenuItem({
                label: 'Github',
                click: () => {
                    currentWindow.webContents.on('new-window', require('electron').shell.openExternal('https://github.com/Gekocaretaker/route'));
                }
            }));
            menu.append(new MenuItem({
                label: 'Discord',
                click: () => {
                    console.log("Hiding the link!");
                }
            }));
            menu.append(new MenuItem({
                label: 'File',
                submenu: [
                    {
                        label: 'Open File',
                        accelerator: 'Ctrl+O',
                        click: () => {
                            openFile();
                        }
                    },
                    {
                        label: 'Close File',
                        accelerator: 'Ctrl+D',
                        click: () => {
                            closeFile();
                        }
                    },
                    {
                        label: 'New File',
                        accelerator: 'Ctrl+N',
                        click: () => {
                            console.log("This is not yet available.");
                        }
                    }
                ]
            }))

            mainTitlebar.updateMenu(menu);
        </script>
        <div class="grid-container">
            <div class="buttons">
                <div class="tooltip openFileContainer">
                    <button id="openFile" class="invisButton"><span class="mdi mdi-file mdi-48px"></span></button>
                    <div>File</div>
                </div>
                <div class="tooltip settingsContainer">
                    <button id="openSettings" class="invisButton"><span class="mdi mdi-cog-outline mdi-48px"></span></button>
                    <div>Settings</div>
                </div>
                <div class="tooltip closeProjectContainer">
                    <button id="closeFile" class="invisButton"><span class="mdi mdi-close mdi-48px"></span></button>
                    <div>Close Project</div>
                </div>
            </div>
            <div class="projects">
                <!-- <div class="project1">
                    <button class="invisButton activeProjectButton" id="project1Click">Project 1</button>
                </div> -->
            </div>
            <div class="jsonEditor">
                <!-- <div class="workSpace1 activeWorkSpace">
                    <h3>This is Workspace 1</h3>
                </div> -->
                <div id="jsonProjectContainer"></div>
                <div class="unHidden">
                    <p id="welcomeMessage">Hello User! Welcome to Route. I hope you enjoy using this!</p>
                </div>
                <div id="updateNotifier" class="hidden">
                    <p id="updateAvailability"></p>
                    <button id="restartButton" onclick="restartApp()"></button>
                </div>
            </div>
            <div class="sidebar">
                <div class="tooltip tradeBuilder">
                    <button id="tradeSetup" class="invisButton"><span class="mdi mdi-apache-kafka mdi-48px"></span></button>
                    <div>Basic Trade</div>
                </div>
                <div class="tooltip whatNext">
                    <button id="whatsNext" class="invisButton"><span class="mdi mdi-help mdi-48px"></span></button>
                    <div>Whats Next?</div>
                </div>
            </div>
          </div>
        <script src="renderer.js"></script>
    </body>
</html>

这是错误:

Uncaught (in promise) TypeError: Cannot read property 'min' of undefined at createProject (renderer.js:126) at renderer.js:48

而且我很确定错误来自我如何获得“min”。我尝试过使用 ["min"]、['min']、.min,并且作为最后的手段,使用 [0]。

如果需要共享,我将代码托管在 github 上。

标签: javascriptjsonobjectelectrontypeerror

解决方案


如果您要调试代码,您会注意到当 j=1 和 k=0 时会发生错误。然后,如果您检查对象,您会注意到在该位置没有question属性:

{
  "tiers": [
    {
      "trades": [
        {
            /* ... */
        },
        {
          "wants": [
            {
              "item": "minecraft:emerald"
              /* no quantity here */
            }
          ],
          "gives": [
              /* ... */
          ]
        }
      ]
    },
    /* ... */

因此,您必须决定在没有quantity财产时要发生什么。例如,您可以使用可选的链接运算符来使用默认值:

let wantsMin = projectArray[projectCount - 1].tiers[i].trades[j].wants[k].quantity.?min || 0;
let wantsMax = projectArray[projectCount - 1].tiers[i].trades[j].wants[k].quantity.?max || 0;

以上将给出 0 作为缺少的最小/最大值的值。但这一切都取决于您希望在这种情况下发生什么。

这只是一个关于如何规避错误的示例,但根据您的其余代码,可能不是处理它的正确方法。这由你来决定。


推荐阅读