JSON 获取数据这件事看着简单真正写起来坑比想象中多。明明接口返回了内容JSON.parse却报错本地文件读取好好的一换成网络请求就全乱套更别提跨域、超时、字段对不上这些老生常谈的问题。这篇文章就从实际开发的角度把“获取 JSON 数据”的完整链路捋一遍——包括浏览器端怎么拿、服务端怎么拿、拿完之后怎么解析、解析完之后怎么排雷直接对标日常开发中最常用的几种姿势你照着写就能用。1. 先搞明白JSON 数据到底从哪来、怎么才能拿到手只要接触过前后端分离的开发模式基本就绕不开 JSON。它本质是 JavaScript Object Notation一套轻量级的数据交换格式长得像 JavaScript 对象但 KEY 必须是双引号包裹的字符串不支持注释也不允许尾逗号。服务端接口返回 JSON前端拿到后渲染页面服务端之间互相调用 API收到的也是 JSON连本地项目的配置文件现在也有大量场景直接用 JSON 承载。所以“获取 JSON 数据”这个动作实际覆盖了本地文件读取、HTTP 接口请求、跨服务调用、甚至从数据库查询结果中直接转换等多个方向。但很多人一上来就钻进 fetch 或者 axios 的语法里忽略了最基础的一件事你拿到的内容到底是“文本字符串”还是“已经解析好的对象”。HTTP 传输通道不区分数据的结构它只负责搬运字节流。所以服务端返回的{name: 张三}在你的程序里先是一个字符串等执行了JSON.parse()或者响应拦截器自动序列化之后才会变成可以data.name直接取值的对象。这个认知如果不建立后面所有代码都会写得稀里糊涂。日常开发里的 JSON 获取场景按来源可以分成三大类静态 JSON 文件本地配置文件、模拟数据、打包进项目的资源文件通常用import、require、文件系统读取等方式直接拿。HTTP API 接口返回这是最主流的场景浏览器端用 fetch / axios / XMLHttpRequest服务端用 Node 的内置模块或第三方库Python 用 requestsJava 用 HttpClient 等等。程序内部产生的 JSON比如把内存对象序列化成 JSON 字符串用于存储再在别的地方反序列化取回或者从消息队列、缓存中间件里拿到的字符串再解析。每一种来源都有自己对应的“获取姿势”和“注意点”下面按实战路径一步步拆。2. 本地 JSON 文件的读取最简单的入口但隐藏着模块差异2.1 浏览器环境下的静态 JSON 引入如果你只是在前端项目里引用一个本地的静态 JSON 文件比如模拟数据、配置项现代构建工具已经把这步做得很透明import mockData from ./mock/data.json console.log(mockData.userInfo)或者更保守一点在支持fetch的前提下用异步方案const response await fetch(./mock/data.json) const data await response.json()这种用法的前提是你的项目启用了打包工具Webpack、Vite、Rollup 这类。工具会在构建阶段把 JSON 转成 JS 模块直接挂到导出对象上所以能像引入普通 JS 一样拿到解析好的数据。实际工作里这招最常用来做本地 mock 数据、站点基础配置、或者静态词表数据。2.2 Node.js / 服务端环境下的 JSON 文件读取如果你在 Node 服务端跑就要格外留意ESM 和 CommonJS 的差异。老项目大多用 CommonJSconst fs require(fs) const path require(path) const raw fs.readFileSync(path.resolve(__dirname, config.json), utf-8) const config JSON.parse(raw)新的 ESM 项目可以直接 import import config from ./config.json with { type: json }也可以配合fs的 promise 版import { readFile } from node:fs/promises const raw await readFile(new URL(./config.json, import.meta.url), utf-8) const config JSON.parse(raw)这里必须强调一个很多人踩过的坑直接 require(./config.json)Node 确实允许这么做但会有缓存问题——同一个文件被多次 require拿到的永远是第一次加载的结果一旦你期望动态热更新配置这条路就走不通了。这种情况下必须用fs重新读文件、重新JSON.parse才可能拿到最新内容。2.3 读取文件时的编码问题读取文本文件时编码必须指定清楚。默认情况下readFileSync如果没传 encoding返回的是一个 Buffer不经过JSON.parse之前你需要手动调用 toString。最稳妥的写法是显式指定utf-8。另外有些 Windows 环境下文件可能是带 BOM 的 UTF-8读出来的字符串开头会有一个不可见字符\uFEFFJSON.parse直接就会抛错。我在实际处理第三方配置文件时遇到过几次解决办法是解析前先去掉 BOMconst raw fs.readFileSync(filePath, utf-8).replace(/^\uFEFF/, )这个小细节能帮你省下好几个小时的排查时间。3. 浏览器端获取 JSON从 fetch 到 axios 的完整姿势3.1 原生 fetch现代浏览器的默认选择浏览器里最标准的获取方式就是 fetch。它基于 Promise写起来比古代神器 XMLHttpRequest 清爽太多了async function getUserInfo(userId) { const url /api/users/${userId} // fetch 默认不带 credentials如果你要带 Cookie 必须显式设置 const response await fetch(url, { method: GET, headers: { Content-Type: application/json, Accept: application/json }, credentials: same-origin }) if (!response.ok) { throw new Error(HTTP error! status: ${response.status}) } const data await response.json() return data }这个片段里有个特别容易被忽视的点fetch 不会在 HTTP 状态码是 404 或 500 时自动把 Promise 置为 rejected它只在网络层面真正失败时才 reject。换句话说response.ok的判断必须自己加否则你拿到的可能是一个错误页面或空数据但代码完全不会感知到异常。刚上手的朋友十有八九会在这里翻车。还有response.json()本身也可能抛错比如服务端返回的内容不是合法 JSON或者响应头里的 Content-Type 不是 application/json 但返回体又确实是 JSON某些浏览器实现会有兼容差异。稳妥的做法是把response.json()放进 try-catch或者用text()先取字符串再手动 parse——这样至少你能把原始内容打出来排查。3.2 用 axios更友好的拦截与错误统一处理如果你工作在一个中大型前端项目里大概率会接触到 axios。它是对 XMLHttpRequest 的封装但做得很聪明——默认情况下当 HTTP 状态码不在 2xx 范围时Promise 会直接 reject这比 fetch 更符合直觉。最常见的封装是这样import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.response.use( (response) { // 这种写法默认后端包裹了一层比如 { code: 0, data: {...}, message: ok } const body response.data if (body.code ! 0) { return Promise.reject(new Error(body.message || 业务错误)) } return body.data }, (error) { // 超时、网络错误、HTTP 错误都在这里统一处理 const message error.code ECONNABORTED ? 请求超时请检查网络 : (error.response?.data?.message || 请求失败) console.error([request error], message) return Promise.reject(error) } )这里需要解释一下response.data为什么已经是对象不是字符串axios 在收到响应后会依据 responseType 配置和 Content-Type 做自动序列化。默认的 responseType 是 json如果响应头是application/jsonaxios 内部会调用JSON.parse然后把解析后的对象赋给你。如果你把 responseType 改成 text那么response.data就会是原始字符串这时候你得自己实现 JSON.parse。实际工作中我用 axios 比较多因为它把请求取消、超时处理、拦截器、并发请求这些都做得很完善尤其是统一拦截器这个设计可以让你把所有接口的公共逻辑——登录态失效跳转、错误 Toast、token 注入——收敛到一处不用每个接口重复写。3.3 XMLHttpRequest还活着但你真的别手动写了上一代方式是用 XMLHttpRequest 获取 JSON代码冗长且回调容易嵌套。到现在它最大的价值就是“当你不想引入 axios、又需要监听上传下载进度时”。实际项目里很少有人直接裸写 XHR 了但了解它的结构能帮你理解现代封装到底解决了什么问题function requestJSON(url) { return new Promise((resolve, reject) { const xhr new XMLHttpRequest() xhr.open(GET, url, true) xhr.responseType json xhr.timeout 10000 xhr.onload () { if (xhr.status 200 xhr.status 300) { resolve(xhr.response) } else { reject(new Error(HTTP ${xhr.status})) } } xhr.onerror () reject(new Error(network error)) xhr.ontimeout () reject(new Error(timeout)) xhr.send() }) }xhr.responseType json是非常关键的一行设置后xhr.response会直接是解析好的对象。如果你不设置就需要JSON.parse(xhr.responseText)再处理。从这里你也能看出前端处理 JSON 获取的路径其实殊途同归——都是“拿文本字符串、再解析成对象”这两个阶段。4. 服务端获取 JSONNode.js 与 Python 的双实践4.1 Node.js 里请求第三方接口服务端代码也一样要获取 JSON 数据场景往往是聚合接口、访问第三方服务、拉取配置中心的数据。Node 18 之后原生支持了fetch可以直接用async function fetchRemoteData() { const res await fetch(https://example.com/api/data, { headers: { Authorization: Bearer ${token}, Accept: application/json } }) if (!res.ok) { const errorText await res.text() throw new Error(Request failed: ${res.status} ${errorText}) } return res.json() }但注意Node 端fetch对超时控制不如专门的请求库方便。如果想精细控制超时得自己配合AbortControllerconst controller new AbortController() const timer setTimeout(() controller.abort(), 5000) try { const res await fetch(url, { signal: controller.signal }) // 处理返回 } catch (err) { if (err.name AbortError) { console.error(请求超时) } } finally { clearTimeout(timer) }这属于 Node 环境的“隐藏细节”一定要自己写好否则遇到慢接口时请求会一直挂着连接池很快会被占满。另一种在 Node 社区非常主流的方案是用 axios和浏览器端同一套 APIimport axios from axios const { data } await axios.get(url, { headers: { Authorization: Bearer ${token} }, timeout: 5000 })这里要特别提醒一个经验点请求库在服务端会比浏览器端更容易受到 DNS 解析、连接池、TLS 握手的影响所以超时设置、重试策略、请求日志一个都不能少。我习惯在服务端封装一个带三次重试的请求函数某种程度比浏览器端更敏感因为服务端承载的是线上流量一次失败可能引发连锁告警。4.2 Python 环境用 requests 获取 JSONPython 生态里用 requests 获取 JSON 是最常见的做法import requests response requests.get(https://example.com/api/data, headers{ Accept: application/json }, timeout5) response.raise_for_status() # 非 2xx 会抛异常 data response.json() # requests 自动处理 JSON 反序列化response.json()内部其实调用了json.loads(response.text)所以接口返回非 JSON 时会抛出requests.exceptions.JSONDecodeError。另外一个实操建议如果返回体比较大直接用线上接口调试时可以先打印response.text[:200]看看原始内容再决定怎么解析。还有response.encoding的问题。有些老接口返回的 Content-Type 没写 charset或者中文编码不是 UTF-8requests 默认会尝试从 headers 里猜猜错时response.text就是乱码。稳妥做法是显式指定response.encoding utf-8这一步虽然简单但能解决一大半“接口返回中文乱码”的问题。5. 拿到 JSON 之后解析、取值、序列化的那些关键细节5.1 JSON.parse 的陷阱与容错把字符串变成对象最简单的方式是JSON.parse()但它对入参极其严格。常见的几个坑尾逗号{a: 1,}直接 SyntaxError。单引号JSON 只认双引号{a: 1}直接失败。undefined / NaN / Infinity这些不是合法 JSON 类型JSON.parse({a: undefined})直接报错。控制字符字符串里包含未转义换行符也会出错。前导零{a: 01}非法。所以在解析“来路不明”的字符串时一定要包 try-catchfunction safeParse(str, fallback null) { try { return JSON.parse(str) } catch (err) { console.error(JSON 解析失败原始内容:, str) return fallback } }生产环境里我几乎从不裸调JSON.parse一定包一层保险。你永远不知道上游会给你返回什么妖魔鬼怪尤其是对接第三方老系统时。5.2 深嵌套数据的安全取值获取到 JSON 对象后最常见的问题是“路径不存在”。比如接口文档说data.userInfo.list[0].name但实际数据里userInfo是 null直接取肯定报错。早年 JavaScript 里最繁琐的就是这个后来有了可选链操作符舒服多了const name data?.userInfo?.list?.[0]?.name ?? 默认值但是再强调一次这只适用于你自己确认结构相对稳定的数据。如果项目的接口文档像段子一样随便改字段再好的取值技巧也救不了。这时候可以考虑写一个归一化函数把所有兼容字段统一映射别把脏数据散落在业务代码里。还有一个小众但常用的方法用JSON.stringify深拷贝配合类型判断function isPlainObject(value) { return Object.prototype.toString.call(value) [object Object] }获取 JSON 后先做结构校验比用到一半再报错要靠谱得多。5.3 将对象转成 JSON 字符串时的类型坑获取和解析的另一面是序列化。JSON.stringify()一样有很多暗坑对象的undefined属性、函数属性、Symbol 属性会被直接丢弃。NaN、Infinity会被序列化成null。Date对象会变成 ISO 字符串而不是你预期的{year:..., month:...}结构。循环引用的对象会抛错。如果 key 是undefined或函数整对键值会被忽略。所以如果你把一些内存对象序列化后塞进缓存或数据库再拿出来解析字段可能凭空变少。排查时最好用Object.keys()对比一下序列化前后的差异。5.4 大数据量 JSON 的性能处理获取超大 JSON 时比如几十 MB 的配置或报表直接JSON.parse会阻塞主线程浏览器里会导致页面卡死。这种情况下有几个方向分片解析如果是数组型大数据可以流式处理。Web Worker把解析放到后台线程避免卡 UI。JSON.parse 的 reviver 只用来做后处理不要指望它优化速度本身只会更慢。Node 服务端处理大 JSON 时JSON.parse的 CPU 开销也不容小觑。我遇到过某个报表服务因为一次解析 50MB 数据压测时响应时间直接翻倍。后来改成流式解析方案内存和耗时都明显下降。所以大数据量节点别偷懒直接用JSON.parse要先分析结构再决定策略。6. 常见问题与排查实录跨域、乱码、超时、缓存一次说透6.1 跨域问题CORS怎么解浏览器获取 JSON 时最经典的问题就是跨域。报错信息通常在 Console 里是 “No ‘Access-Control-Allow-Origin’ header is present on the requested resource”。根源是浏览器的同源策略你页面在 a.com却向 b.com 发起 XHR/fetch 拿数据默认是不允许的。常见的三种解法按优先级排列服务端加 CORS 响应头。目标接口的响应带上Access-Control-Allow-Origin: 你的域名必要时处理 preflight 请求。这是正规解法前提是你对服务端有控制权。开发环境配代理。不少前端脚手架都有代理配置把/api转发到目标服务器这样从浏览器看是同源请求绕开了跨域限制。JSONP老式方案只支持 GET而且只能配合 script 标签动态加载来拿到数据。现在用得越来越少遇到老系统时才会用到。还有一种情况是你自己本地调试时跨域开一个临时代理或者直接用开发服务器的 proxy 配置比折腾浏览器禁跨域靠谱得多。6.2 接口返回乱码获取到 JSON 字符串后打印出来发现中文全是…这种乱码大概率是编码问题。前端最常见的是接口响应头没有charsetutf-8而浏览器默认按其他编码解析。解决办法如果是自己后端服务把响应头统一设成Content-Type: application/json; charsetutf-8。如果是 Node 端拿数据可以把响应体按 Buffer 拿下来再用iconv-lite等各种转码工具做转换。如果只是联调阶段的数据检查接口文档的编码声明别盲目换库。Python 的 requests 端在前面已经提过response.encoding utf-8是最快的止血方案。6.3 拿到的是 null 或 undefined好多状况是请求没报错但console.log(data)打出null。这通常有几类原因接口返回的就是字符串 nullJSON.parse后得到null。这种情况往往是上游服务端逻辑没数据时随便返回了个 null。取值前先判空。HTTP 204 No Contentresponse.json() 没东西可解析浏览器端会报错。异步时序问题你在 Promise 外层还没 await 完成时就尝试取值结果拿到的是 Promise 对象本身或 undefined。新手朋友喜欢在调用函数后立刻访问返回值需要检查是否漏了await。针对异步时序这类问题我习惯用一个小模板async function loadData() { const data await getRemoteJson() render(data) }所有数据获取动作都放进 async 函数然后显式 await绝不直接在外层同步取值。6.4 缓存带来的“假数据”浏览器和 HTTP 中间层都可能缓存 GET 请求。如果你改了接口返回内容但页面上拿到的还是旧 JSON十有八九是缓存。排查方法看 Network 面板如果请求显示 from disk cache / memory cache那基本就是缓存命中。带上时间戳参数或请求头Cache-Control: no-cache。axios 请求里加params: { _t: Date.now() }是一个简单粗暴但有效的姿势。服务端请求第三方接口时同样要留意上游 CDN 的缓存策略必要时在请求头加明确的Cache-Control。6.5 请求超时与重试获取 JSON 数据时服务端响应慢是常态。超时设置只有一条原则没有设置超时的请求是最危险的请求。浏览器端 fetch 不会默认超时axios 里默认也无超时必须自己配。超时之后的策略更重要。建议用指数退避重试而不是固定时间重试async function fetchWithRetry(url, retries 3) { let lastError for (let i 0; i retries; i) { try { const res await fetch(url) if (!res.ok) throw new Error(HTTP ${res.status}) return await res.json() } catch (err) { lastError err const waitMs Math.min(1000 * 2 ** i, 3000) // 指数退避 await new Promise(resolve setTimeout(resolve, waitMs)) } } throw lastError }注意重试只适合幂等请求GET、PUT 等对会产生副作用的 POST 请求要慎重避免重复扣款、重复下单这类事故。这一点做交易系统的朋友应该深有体会。7. 给新手的五个建议如果你今天才开始接触 JSON 获取数据我建议从这几个方面练手先抓包看原始响应。无论浏览器还是服务端拿到数据后第一件事不是急着取值而是console.log(res.text)或打印 response 字符串看清楚返回结构再动手。养成统一封装请求函数的习惯。哪怕只是一个小工具函数把超时、错误处理、重试都包起来后面项目复杂度上来时会省很多事。为所有网络请求设计失败出口。获取数据失败时页面要显示什么、服务端要返回什么必须在一开始就想明白。很多人只写“成功路径”失败路径全靠用户刷新。健壮性靠校验不靠运气。用安全解析函数、判空、类型检查避免“上游只要一改字段我就跟着改十几个文件”的被动局面。把 JSON 数据获取和业务解耦。所有数据源统一走数据层页面组件不直接碰 fetch这样后续换接口、改字段、做缓存都能控制在局部。我自己的体会是JSON 获取数据本身并不是一道难题难点几乎都集中在“边界情况”上——数据缺失、格式不合法、接口超时、缓存干扰。你把防守型的写法变成肌肉记忆未来遇到任何返回不稳定的接口都能平稳接住。这个技术点看起来基础但恰恰是它撑起了前端工程里最日常、最重要的一条数据链路。
阅读完成 · 觉得有帮助?