关于本文
本文主要总结网站编写以来在传递 JSON 数据方面遇到的一些问题以及目前采用的解决方案。网站数据库采用 MongoDB,后端是 Python,前端采用“半分离”形式的 Riot.js,所谓半分离,是说第一页数据是通过服务器端的模板引擎直接渲染到 HTML 中,从而避免首页两次加载的问题,而其它动态内容则采用 Ajax 加载。整个流程中数据都是通过 JSON 格式传递的,但是在不同的环节中需要采用不同的方式并遇到一些不同的问题,本文主要做记录、总结。
1. What is JSON"string"
数字:数字
布尔值: true / false
了解了 JSON 的基本概念之后,下面分别针对上图中的几个数据交互环节进行总结。
2. Python <=> MongoDB
Python 与 MongoDB 之间的交互主要由现有的驱动库提供支持,包括 PyMongo、Motor 等,而这些驱动所提供的接口都是非常友好的,我们不需要了解任何底层的实现,只要对 Python 原生的字典类型进行操作即可:
import motor client = motor.motor_tornado.MotorClient() db = client['test'] user_col = db['user'] user_col.insert(dict( name = 'Yu', is_admin = True, ))
唯一需要注意的是 MongoDB 中的索引项 _id 是通过 ObjectId("572df0b78a83851d5f24e2c1") 存储的,而对应的 Python 对象为 bson.objectid.ObjectId ,因此在查询时需要以此对象的实例进行:
from bson.objectid import ObjectId user = db.user.find_one(dict( _id = ObjectId("572df0b78a83851d5f24e2c1") ))
3. Python <=> Ajax
前端与后端之间的数据交流比较常用的是通过 Ajax 完成,这时遇到了第一个不大不小的坑。在之前的一篇文章中,我总结了 一次 Python 编码的坑 ,我们知道 HTTP 传递过程中肯定不存在 JSON/XML ,一切都是二进制数据,但是我们可以选择让前端用什么样的方式解读这些数据,即通过设定 Header 中的 Content-Type ,一般传递 JSON 数据时将其设定为 Content-Type: application/json ,在 Tornado 最新版本中,只需要直接写入字典类型即可:
# Handler async def post(self): user = await self.db.user.find_one({}) self.write(user)
于是迎来了第一个错误: TypeError: ObjectId('572df0b58a83851d5f24e2b1') is not JSON serializable 。追溯原因,虽然 Tornado 帮我们简化了操作,但在像 HTTP 中写入字典类型时仍然需要经历一次 json.dumps(user) 操作,而对于 json.dumps 来说, 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))
这次不会再出错了,我们自己的 JSONEncoder 可以应对 ObjectId 了,但另一个问题也出现了:
JSONEncoder.encode 之后字典类型被转换成字符串,写入 HTTP 之后 Content-Type 变为 text/html ,这时前端将认为接收的数据为字符串而不是可用的 JavaScript Object。当然还有进一步的弥补方案,那就是前端再进行一次转换:
$.post(API, {}, function(res){ data = JSON.parse(res); console.log(data._id); })
问题暂时解决了,在整个过程中 JSON 的变换是这样的:
Python ==> json.dumps ==> HTTP ==> JavaScript ==> JSON.parse dict ==> str ==> binary ==> string ==> Object
结果第二个问题来了,当数据中存在一些特殊字符时, JSON.parse 将出现错误:
JSON.parse("{'abs': '\n'}"); // VM536:1 Uncaught SyntaxError: Unexpected token ' in JSON at position 1(…)
这就是在遇到问题是只着眼解决眼前错误导致后续一连串改动所带来的弊病。我们沿着上面 JSON 变换的链条向上追溯,看有没有更好的解决方案。很简单, 遵循传统规则,出现特例的时候,改变自身适应规则,而不是改变规则 :
# Handler async def post(self): user = await self.db.user.find_one({}) user['_id'] = str(user['_id']) self.write(user)
当然,如果是多条数据的列表形式,还需要进一步改造:
# 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
如果上面的问题可以通过 遵守规则 来解决,那么接下来这个问题就是一个挑战规则的故事。除去 Ajax 动态加载部分,网页上的其他数据是通过后端模板引擎渲染得来的,也就是说是 Hard-coding 为 HTML 的。在浏览器加载并解析这个 HTML 文件之前它们只是纯文本文件,而我们需要的是直接将数据塞仅 <script> 标签在浏览器运行 JavaScript 时直接可用。严格意义上来说这并不算是 JSON 的应用,而是 Python 的 dict 与 JavaScript 的 Object 之间的直接转换,常规的方法应该这样写:
# 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>
这样写是对的,但是要解决上面提到的 ObjectId() 问题还是需要一些额外的处理(尤其是引号问题)。另外为了解决 ObjectId 的问题我还尝试了一种比较蠢的方法(在上面的 JSON.parse 遇到错误之前):
# 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>
其实跟第 3 小节的问题一样,模板引擎渲染过程与 HTTP 传输过程是类似的,不同的是在模板中字符串变量就是纯粹的值(没有引号),因此完全可以用生成 JavaScript 脚本文件的形式渲染变量而无需顾虑特殊字符(下面的 {% raw ... %} 是 Tornado 模板用于防止特殊符号被 HTML 编码的语法):
<!-- HTML + Riot.js --> <app></app> <script> riot.mount('app', { users: {% raw users %}), }) </script>
总结
JSON 是很好用的数据格式,但是在不同语言环境之间切换还是有很多细节问题需要注意。此外, 遵循传统规则,出现特例的时候,改变自身适应规则,而不是试图改变规则 ,这一条不一定适应所有问题,但对于那些已被公认的规则,请勿轻易挑战。
以上所述是小编给大家介绍的JSON 的正确用法探讨:Pyhong、MongoDB、JavaScript与Ajax的相关知识,希望对大家有所帮助,如果大家想了解更多资讯敬请关注网站!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 中国武警男声合唱团《辉煌之声1天路》[DTS-WAV分轨]
- 紫薇《旧曲新韵》[320K/MP3][175.29MB]
- 紫薇《旧曲新韵》[FLAC/分轨][550.18MB]
- 周深《反深代词》[先听版][320K/MP3][72.71MB]
- 李佳薇.2024-会发光的【黑籁音乐】【FLAC分轨】
- 后弦.2012-很有爱【天浩盛世】【WAV+CUE】
- 林俊吉.2012-将你惜命命【美华】【WAV+CUE】
- 晓雅《分享》DTS-WAV
- 黑鸭子2008-飞歌[首版][WAV+CUE]
- 黄乙玲1989-水泼落地难收回[日本天龙版][WAV+CUE]
- 周深《反深代词》[先听版][FLAC/分轨][310.97MB]
- 姜育恒1984《什么时候·串起又散落》台湾复刻版[WAV+CUE][1G]
- 那英《如今》引进版[WAV+CUE][1G]
- 蔡幸娟.1991-真的让我爱你吗【飞碟】【WAV+CUE】
- 群星.2024-好团圆电视剧原声带【TME】【FLAC分轨】