JSON 的正確用法探討:Pyhong、MongoDB、JavaScript與Ajax
關(guān)于本文
本文主要總結(jié)網(wǎng)站編寫以來(lái)在傳遞 JSON 數(shù)據(jù)方面遇到的一些問(wèn)題以及目前采用的解決方案。網(wǎng)站數(shù)據(jù)庫(kù)采用 MongoDB,后端是 Python,前端采用“半分離”形式的 Riot.js,所謂半分離,是說(shuō)第一頁(yè)數(shù)據(jù)是通過(guò)服務(wù)器端的模板引擎直接渲染到 HTML 中,從而避免首頁(yè)兩次加載的問(wèn)題,而其它動(dòng)態(tài)內(nèi)容則采用 Ajax 加載。整個(gè)流程中數(shù)據(jù)都是通過(guò) JSON 格式傳遞的,但是在不同的環(huán)節(jié)中需要采用不同的方式并遇到一些不同的問(wèn)題,本文主要做記錄、總結(jié)。
1. What is JSON?
JSON(JavaScript Object Notation) 是一種由道格拉斯·克羅克福特構(gòu)想設(shè)計(jì)、輕量級(jí)的數(shù)據(jù)交換語(yǔ)言,它的前輩 XML 可能更早被人們所熟知。當(dāng)然 JSON 并不是為了取代 XML 而存在的,只是相比于 XML 它更小巧、更適合在網(wǎng)頁(yè)開(kāi)發(fā)中用作數(shù)據(jù)傳遞(JSON 之于 JavaScript 就像 XML 之于 Lisp)。從名字上可以看出,JSON 的格式符合 JavaScript 語(yǔ)言中“對(duì)象”的語(yǔ)法格式,除了 JavaScript 之外,很多其他語(yǔ)言中也具有類似的類型,例如 Python 中的字典( dict ),除了編程語(yǔ)言之外,一些基于文檔存儲(chǔ)的 NoSQL 非關(guān)系型數(shù)據(jù)庫(kù)也選擇 JSON 作為其數(shù)據(jù)存儲(chǔ)格式,例如 MongoDB。
總的來(lái)說(shuō),JSON 定義一種標(biāo)記格式,可以非常方便地在編程語(yǔ)言中的變量數(shù)據(jù)與字符串文本數(shù)據(jù)之間相互轉(zhuǎn)換。JSON 描述的數(shù)據(jù)結(jié)構(gòu)包括以下這幾種形式:
對(duì)象: {key: value}
列表: [obj, obj,...]
字符串: "string"
數(shù)字:數(shù)字
布爾值: true / false
了解了 JSON 的基本概念之后,下面分別針對(duì)上圖中的幾個(gè)數(shù)據(jù)交互環(huán)節(jié)進(jìn)行總結(jié)。
2. Python <=> MongoDB
Python 與 MongoDB 之間的交互主要由現(xiàn)有的驅(qū)動(dòng)庫(kù)提供支持,包括 PyMongo、Motor 等,而這些驅(qū)動(dòng)所提供的接口都是非常友好的,我們不需要了解任何底層的實(shí)現(xiàn),只要對(duì) Python 原生的字典類型進(jìn)行操作即可:
import motor client = motor.motor_tornado.MotorClient() db = client['test'] user_col = db['user'] user_col.insert(dict( name = 'Yu', is_admin = True, ))
唯一需要注意的是 MongoDB 中的索引項(xiàng) _id 是通過(guò) ObjectId("572df0b78a83851d5f24e2c1") 存儲(chǔ)的,而對(duì)應(yīng)的 Python 對(duì)象為 bson.objectid.ObjectId ,因此在查詢時(shí)需要以此對(duì)象的實(shí)例進(jìn)行:
from bson.objectid import ObjectId user = db.user.find_one(dict( _id = ObjectId("572df0b78a83851d5f24e2c1") ))
3. Python <=> Ajax
前端與后端之間的數(shù)據(jù)交流比較常用的是通過(guò) Ajax 完成,這時(shí)遇到了第一個(gè)不大不小的坑。在之前的一篇文章中,我總結(jié)了 一次 Python 編碼的坑 ,我們知道 HTTP 傳遞過(guò)程中肯定不存在 JSON/XML ,一切都是二進(jìn)制數(shù)據(jù),但是我們可以選擇讓前端用什么樣的方式解讀這些數(shù)據(jù),即通過(guò)設(shè)定 Header 中的 Content-Type ,一般傳遞 JSON 數(shù)據(jù)時(shí)將其設(shè)定為 Content-Type: application/json ,在 Tornado 最新版本中,只需要直接寫入字典類型即可:
# Handler async def post(self): user = await self.db.user.find_one({}) self.write(user)
于是迎來(lái)了第一個(gè)錯(cuò)誤: TypeError: ObjectId('572df0b58a83851d5f24e2b1') is not JSON serializable 。追溯原因,雖然 Tornado 幫我們簡(jiǎn)化了操作,但在像 HTTP 中寫入字典類型時(shí)仍然需要經(jīng)歷一次 json.dumps(user) 操作,而對(duì)于 json.dumps 來(lái)說(shuō), ObjectId 類型是非法的。于是我選擇了最直觀的解決方案:
import json from bson.objectid import ObjectId class JSONEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, ObjectId): return str(obj) return super().default(self, obj) # Handler async def post(self): user = await self.db.user.find_one({}) self.write(JSONEncoder.encode(user))
這次不會(huì)再出錯(cuò)了,我們自己的 JSONEncoder 可以應(yīng)對(duì) ObjectId 了,但另一個(gè)問(wèn)題也出現(xiàn)了:
JSONEncoder.encode 之后字典類型被轉(zhuǎn)換成字符串,寫入 HTTP 之后 Content-Type 變?yōu)?text/html ,這時(shí)前端將認(rèn)為接收的數(shù)據(jù)為字符串而不是可用的 JavaScript Object。當(dāng)然還有進(jìn)一步的彌補(bǔ)方案,那就是前端再進(jìn)行一次轉(zhuǎn)換:
$.post(API, {}, function(res){ data = JSON.parse(res); console.log(data._id); })
問(wèn)題暫時(shí)解決了,在整個(gè)過(guò)程中 JSON 的變換是這樣的:
Python ==> json.dumps ==> HTTP ==> JavaScript ==> JSON.parse dict ==> str ==> binary ==> string ==> Object
結(jié)果第二個(gè)問(wèn)題來(lái)了,當(dāng)數(shù)據(jù)中存在一些特殊字符時(shí), JSON.parse 將出現(xiàn)錯(cuò)誤:
JSON.parse("{'abs': '\n'}"); // VM536:1 Uncaught SyntaxError: Unexpected token ' in JSON at position 1(…)
這就是在遇到問(wèn)題是只著眼解決眼前錯(cuò)誤導(dǎo)致后續(xù)一連串改動(dòng)所帶來(lái)的弊病。我們沿著上面 JSON 變換的鏈條向上追溯,看有沒(méi)有更好的解決方案。很簡(jiǎn)單, 遵循傳統(tǒng)規(guī)則,出現(xiàn)特例的時(shí)候,改變自身適應(yīng)規(guī)則,而不是改變規(guī)則 :
# Handler async def post(self): user = await self.db.user.find_one({}) user['_id'] = str(user['_id']) self.write(user)
當(dāng)然,如果是多條數(shù)據(jù)的列表形式,還需要進(jìn)一步改造:
# DB async def get_top_users(self, n = 20): users = [] async for user in self.db.user.find({}).sort('rank', -1).limit(n): user['_id'] = str(user['_id']) users.append(user) return users
4. Python <=> HTML+Riot.js
如果上面的問(wèn)題可以通過(guò) 遵守規(guī)則 來(lái)解決,那么接下來(lái)這個(gè)問(wèn)題就是一個(gè)挑戰(zhàn)規(guī)則的故事。除去 Ajax 動(dòng)態(tài)加載部分,網(wǎng)頁(yè)上的其他數(shù)據(jù)是通過(guò)后端模板引擎渲染得來(lái)的,也就是說(shuō)是 Hard-coding 為 HTML 的。在瀏覽器加載并解析這個(gè) HTML 文件之前它們只是純文本文件,而我們需要的是直接將數(shù)據(jù)塞僅 <script> 標(biāo)簽在瀏覽器運(yùn)行 JavaScript 時(shí)直接可用。嚴(yán)格意義上來(lái)說(shuō)這并不算是 JSON 的應(yīng)用,而是 Python 的 dict 與 JavaScript 的 Object 之間的直接轉(zhuǎn)換,常規(guī)的方法應(yīng)該這樣寫:
# Handler async def get(self): users = self.db.get_top_users() render_data = dict( users = users ) self.render('users.html', **render_data) <!-- HTML + Riot.js --> <app></app> <script> riot.mount('app', { users: [ {% for user in users %} { name: "{{ user['name']}}", is_admin: "{{ user['is_admin']}}" }, {% end %} ], }) </script>
這樣寫是對(duì)的,但是要解決上面提到的 ObjectId() 問(wèn)題還是需要一些額外的處理(尤其是引號(hào)問(wèn)題)。另外為了解決 ObjectId 的問(wèn)題我還嘗試了一種比較蠢的方法(在上面的 JSON.parse 遇到錯(cuò)誤之前):
# Handler async def get(self): users = self.db.get_top_users() render_data = dict( users = JSONEncoder.encode(users) ) self.render('users.html', **render_data) <!-- HTML + Riot.js --> <app></app> <script> riot.mount('app', { users: JSON.parse('{{ users }}'), }) </script>
其實(shí)跟第 3 小節(jié)的問(wèn)題一樣,模板引擎渲染過(guò)程與 HTTP 傳輸過(guò)程是類似的,不同的是在模板中字符串變量就是純粹的值(沒(méi)有引號(hào)),因此完全可以用生成 JavaScript 腳本文件的形式渲染變量而無(wú)需顧慮特殊字符(下面的 {% raw ... %} 是 Tornado 模板用于防止特殊符號(hào)被 HTML 編碼的語(yǔ)法):
<!-- HTML + Riot.js --> <app></app> <script> riot.mount('app', { users: {% raw users %}), }) </script>
總結(jié)
JSON 是很好用的數(shù)據(jù)格式,但是在不同語(yǔ)言環(huán)境之間切換還是有很多細(xì)節(jié)問(wèn)題需要注意。此外, 遵循傳統(tǒng)規(guī)則,出現(xiàn)特例的時(shí)候,改變自身適應(yīng)規(guī)則,而不是試圖改變規(guī)則 ,這一條不一定適應(yīng)所有問(wèn)題,但對(duì)于那些已被公認(rèn)的規(guī)則,請(qǐng)勿輕易挑戰(zhàn)。
以上所述是小編給大家介紹的JSON 的正確用法探討:Pyhong、MongoDB、JavaScript與Ajax的相關(guān)知識(shí),希望對(duì)大家有所幫助,如果大家想了解更多資訊敬請(qǐng)關(guān)注腳本之家網(wǎng)站!
- 利用Mongoose讓JSON數(shù)據(jù)直接插入或更新到MongoDB
- python讀取json文件并將數(shù)據(jù)插入到mongodb的方法
- MongoDB執(zhí)行mongoexport時(shí)的異常及分析(數(shù)字類型的查詢)
- MongoDB批量將時(shí)間戳轉(zhuǎn)為通用日期格式示例代碼
- Python實(shí)現(xiàn)批量讀取圖片并存入mongodb數(shù)據(jù)庫(kù)的方法示例
- Mongodb批量刪除gridfs文件實(shí)例
- MongoDB單表數(shù)據(jù)的導(dǎo)出和恢復(fù)實(shí)例講解
- MongoDB備份、還原、導(dǎo)出、導(dǎo)入、克隆操作示例
- 深入分析Mongodb數(shù)據(jù)的導(dǎo)入導(dǎo)出
- MongoDB導(dǎo)出查詢結(jié)果到文件例子
- mongodb 數(shù)據(jù)庫(kù)操作--備份 還原 導(dǎo)出 導(dǎo)入
- MongoDB使用mongoexport和mongoimport命令,批量導(dǎo)出和導(dǎo)入JSON數(shù)據(jù)到同一張表的實(shí)例
相關(guān)文章
Bootstrap彈出帶合法性檢查的登錄框?qū)嵗a【推薦】
這篇文章主要介紹了Bootstrap彈出帶合法性檢查的登錄框?qū)嵗a【推薦】的相關(guān)資料,需要的朋友可以參考下2016-06-06JavaScript實(shí)現(xiàn)獲取設(shè)備網(wǎng)絡(luò)連接信息
作為前端開(kāi)發(fā),做好用戶體驗(yàn)是很重要的,日常開(kāi)發(fā)中我們經(jīng)??梢杂龅接脩艟W(wǎng)速慢導(dǎo)致靜態(tài)資源加載慢,從而給影響用戶體驗(yàn),所以本文來(lái)和大家分享一個(gè)有趣的API,可以實(shí)現(xiàn)獲取網(wǎng)絡(luò)信息2023-05-05基于iframe實(shí)現(xiàn)類似于ajax的頁(yè)面無(wú)刷新
本方法是基于iframe實(shí)現(xiàn)的,需求是form表單提交帶有文件上傳的input標(biāo)簽,示例如下,感興趣的朋友可以參考下2014-05-05JavaScript獲取網(wǎng)頁(yè)、瀏覽器、屏幕高度和寬度匯總
這篇文章主要匯總介紹了JavaScript獲取網(wǎng)頁(yè)、瀏覽器、屏幕高度和寬度的方法,非常使用,有需要的小伙伴參考下。2014-12-12基于JavaScript實(shí)現(xiàn)簡(jiǎn)單的輪播圖
這篇文章主要為大家詳細(xì)介紹了基于JavaScript實(shí)現(xiàn)簡(jiǎn)單的輪播圖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03幾種設(shè)置表單元素中文本輸入框不可編輯的方法總結(jié)
這篇文章主要是對(duì)幾種設(shè)置表單元素中文本輸入框不可編輯的方法進(jìn)行了總結(jié)介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-11-11微信小程序與webview?H5交互的方法(內(nèi)嵌H5跳轉(zhuǎn)原生頁(yè)面)
小程序webView中嵌套H5頁(yè)面,難免會(huì)遇到小程序與h5頁(yè)面進(jìn)行數(shù)據(jù)通信或交互的場(chǎng)景,下面這篇文章主要給大家介紹了關(guān)于微信小程序與webview?H5交互的相關(guān)資料,內(nèi)嵌H5跳轉(zhuǎn)原生頁(yè)面,需要的朋友可以參考下2022-11-11H5喚醒APP實(shí)現(xiàn)方法及注意點(diǎn)總結(jié)
目前通過(guò)H5頁(yè)面喚起App的場(chǎng)景十分常見(jiàn),比如常見(jiàn)的分享功能,這篇文章主要給大家介紹了關(guān)于H5喚醒APP實(shí)現(xiàn)方法及注意點(diǎn)的相關(guān)資料,需要的朋友可以參考下2021-06-06