JSON文件读写

  • 简介
  • 相关知识
  • 环境
  • 准备数据
  • 基本步骤
  • 读(ASCLL)
  • 读(UTF-8)
  • 写(ASCLL)
  • 写(UTF-8)
  • 综合案例

简介

这章将讲述JSON文件的读写,使用的解析库是RapidJSON

这个库的评价和性能还是蛮好的,官方网站也有很详细的教程和文档,这里只记录一些比较基础的

第一个例子,对本专栏第一篇文章中讲到的英雄联盟数据进行简单的读写操作。

第二个例子,进行稍微复杂的读取操作,从JSON文件中读取俄罗斯方块的方块数据。

相关知识

首先非常清晰的明白个概念
JSON节点只有Object和Array两种类型。Object是一个键值对,key=>value, key必须是字符串,value可以是布尔值、整型等各种类型。

RapidJSON中的节点只有rapidjson::Value类型,泛指Object和Array,如果你换成JAVA,JS等其它语言,大多数分开的JSONObject或者JSONArray,这一点差异可能会带来一定的理解障碍,很多人觉得这点很奇怪,我觉得正好相反。

环境

下载

RapidJSON官方网站

安装

因为RapidJSON只有头文件,用的时候用到include头文件

在Visual Studio里面项目属性如下图配置

准备数据

原文件非常复杂的,简单起见,这里只提取出部分数据,包含几种基本的数据类型

文件:./json/Riven.json

{"id": "Riven","key": "92","name": "放逐之刃","title": "锐雯","skins": [ 1, 2, 3, 4, 5 ],"stats": {"hp": 560,"mp": 0,"movespeed": 340,"armor": 33,"spellblock": 32.1,"attackrange": 125,"crit": 0,"attackdamage": 64,"attackspeed": 0.625}
}

我的电脑(中文Windows 10)上面创建.txt时,默认编码是GB2312,需要另存为UTF-8

基本步骤

  1. 从文件中读取数据
  2. 使用RapidJSON解析库进行解析,
    1. 将字符串转换成rapidjson::Document
    2. rapidjson::Document查询变量

读(ASCLL)

用到的头文件
这里我们直接使用上一章从文件读取字符串的函数

#include "BaseFileIO.h"
//Read
#include "rapidjson/document.h"
//Write
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "rapidjson/prettywriter.h"

为了方便解析,在读取文件之后我们直接交给Rapid进行解析,然后返回rapidjson::Document以便后面获取变量。

rapidjson::Document GetDocument(const char* fileName) {rapidjson::Document doc;char* ptr = GetCharsV(fileName);doc.Parse(ptr);delete ptr;return doc;
}

测试用例

void ReadJSON() {//1.从文件中读取数据,将字符串转换成`rapidjson::Document`const char* fileName = "./json/Riven.json";rapidjson::Document doc = GetDocument(fileName);if (!doc.IsObject()) {cout << "Faild to valid JSON";return;}//2.从`rapidjson::Document`获取变量//拷贝到变量中使用string id = doc["id"].GetString();cout << "id:" << id << " characters size:" << id.size() << " id[0]:" << id[0] << endl;cout << "key:" << doc["key"].GetString() << endl;string name = doc["name"].GetString();cout << "name:" << name << " characters size:" << name.size() << " key[0]:" << name[0] << endl;cout << "title:" << doc["title"].GetString() << endl;//遍历数组cout << "skins:";for (auto& kv : doc["skins"].GetArray()) {cout << kv.GetInt() << ",";}cout << endl;//深层变量rapidjson::Value& vStats = doc["stats"];rapidjson::Value& vHp = vStats["hp"];rapidjson::Value& vSpellblock = vStats["spellblock"];cout << "hp:" << vHp.GetInt() << endl;cout << "spellblock:" << vSpellblock.GetDouble() << endl;
}

输出

MBaseIO::GetCharsV:
gcount:308      size:325                strlen:308      vec.size(): 326
{"id": "Riven","key": "92","name": "鏀鹃€愪箣鍒?,"title": "閿愰洴","skins": [ 1, 2, 3, 4, 5 ],"stats": {"hp": 560,"mp": 0,"movespeed": 340,"armor": 33,"spellblock": 32.1,"attackrange": 125,"crit": 0,"attackdamage": 64,"attackspeed": 0.625}
}
id:Riven characters size:5 id[0]:R
key:92
title:閿愰洴箣鍒?characters size:12 key[0]:?
skins:1,2,3,4,5,
hp:560
spellblock:32.1

数值和ASCLL正常获取,中文显然又开始乱码了,要解决这个问题,不是简单的将GetChars换成GetWChars就行了。rapidjson默认的Document和Value是使用char作为解析单元的。

读(UTF-8)

这个RapidJSON有个“诡异”的地方,首先来看一下它Document和Value的定义

//! GenericValue with UTF8 encoding
typedef GenericValue<UTF8<> > Value;
typedef GenericDocument<UTF8<> > Document;
///
// UTF8
//! UTF-8 encoding.
/*! http://en.wikipedia.org/wiki/UTF-8http://tools.ietf.org/html/rfc3629\tparam CharType Code unit for storing 8-bit UTF-8 data. Default is char.\note implements Encoding concept
*/
template<typename CharType = char>
struct UTF8;

Document和Value默认解码方式为UTF8,而UTF8又是使用char来存放。

我们都知道一个UTF-8码点的宽度是1到4个字节,char很显然不能满足,而wchar_t能够满足这个条件

为什么RaidJSON不用wchar_t而是char呢?

RapidJSON:Encoding
Character Type
As shown in the declaration, each encoding has a CharType template parameter. Actually, it may be a little bit confusing, but each CharType stores a code unit, not a character (code point). As mentioned in previous section, a code point may be encoded to 1–4 code units for UTF-8.

大概意思
每一个编码都有一个Character Type模板参数,实际上Character Type存储的是一个编码单位,而不是一个字符,在UTF8中一个字符可能会编码成1到4个char。

也就是说在rapidjson中,char 是UTF-8的编码单位,把char用作存储单位无可厚非,因为对数据的整体结构没有影响,解析库根据逗号,引号,分号这些来“断句”

关键问题是在查询变量的时候,GetString只能获得一个char类型的编码单位,而我们想要取出来的是完整的字符,而不是三分之一的字符,所以这个地方,如果任然使用rapidjson默认的UTF8的Character Type就会导致这种情况的发生。

我的解决方法就是,任然使用UTF-8进行解码,但是将存储单位换成wchar_t,最终将GetDocument升级成GetWDocument

typedef rapidjson::GenericDocument<rapidjson::UTF8<wchar_t> > WDocument;
typedef rapidjson::GenericValue<rapidjson::UTF8<wchar_t> > WValue;
WDocument GetWDocument(const char* fileName) {WDocument doc;wchar_t* ptr = GetWCharsV(fileName);doc.Parse(ptr);delete ptr;return doc;
}

新的测试用例

不一定全部都要用wcout,忘记改了

void ReadWJSON() {const char* fileName = "./json/Riven.json";WDocument doc = GetWDocument(fileName);if (!doc.IsObject()) {cout << "Faild to valid JSON";return;}wstring id = doc[L"id"].GetString();wcout << L"id:" << id << "\tid.size()" << id.size() << "\tid[0]:" << id[0] << L"\n";wcout << L"key:" << doc[L"key"].GetString() << L"\n";wstring name = doc[L"name"].GetString();wcout << L"name:" << name << L"\tname.size():" << name.size() << L"\tname[0]:" << name[0] << L"\n";wcout << L"title:" << doc[L"title"].GetString() << L"\n";cout << "skins:";for (auto& kv : doc[L"skins"].GetArray()) {cout << kv.GetInt() << ",";}cout << endl;WValue& vStats = doc[L"stats"];WValue& vHp = vStats[L"hp"];WValue& vSpellblock = vStats[L"spellblock"];cout << "hp:" << vHp.GetInt() << endl;cout << "spellblock:" << vSpellblock.GetDouble() << endl;//更改之后保存const char* newName = "./json/Riven-Update.json";vHp.SetInt(100000);SaveWRapidJson(doc, newName);
}

输出

MBaseIO::GetWCharsV:
gcount:296      size:325                wcslen: 296     vec.size(): 326
{"id": "Riven","key": "92","name": "放逐之刃","title": "锐雯","skins": [ 1, 2, 3, 4, 5 ],"stats": {"hp": 560,"mp": 0,"movespeed": 340,"armor": 33,"spellblock": 32.1,"attackrange": 125,"crit": 0,"attackdamage": 64,"attackspeed": 0.625}
}
id:Riven        id.size()5      id[0]:R
key:92
name:放逐之刃   name.size():4   name[0]:放
title:锐雯
skins:1,2,3,4,5,
hp:560
spellblock:32.1MBaseIO::OverWriteUTF8:
wcslen(content):393

写(ASCLL)

这个过程也是比较简单的,就是把Document先转换成char然后在调用上一章里面保存char的函数去进行保存

bool SaveRapidJson(rapidjson::Document& doc, const char* fileName) {//1.Document=>char*rapidjson::StringBuffer buffer;rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);doc.Accept(writer);const char* output = buffer.GetString();//2.char*=>fileOverWriteFile(fileName, output);return true;
}

StringBuffer类似于vector

在rapidjson中,PrettyWriter和Writer都可以用来保存数据

Writer是一个handle,accept将自己交给writer处理,writer将doc的字符串输入给buffer
最后将buffer中的字符串保存到文件,完成JSON的写出

Writer输出的是没有缩进和换行的字符串,适合于程序内部和网络传输,具有同样功能的PrettyWriter可以输出缩进和换行,但看起来有些冗长

测试用例

//从零构建一个JSON DOM并写入文件
void WriteJSON() {//1.初始化Documentrapidjson::Document doc;doc.SetObject();//2.添加内容doc.AddMember("key1", "string", doc.GetAllocator());//Objectrapidjson::Value key2(rapidjson::kObjectType);key2.AddMember("key2.1", 123, doc.GetAllocator());key2.AddMember("key2.2", true, doc.GetAllocator());doc.AddMember("key2", key2, doc.GetAllocator());doc.AddMember("key3", 1.23, doc.GetAllocator());//Arrayrapidjson::Value key4(rapidjson::kArrayType);for (int i = 1; i < 9; i++) {key4.PushBack(i, doc.GetAllocator());}doc.AddMember("key4", key4, doc.GetAllocator());//3.保存到文件SaveRapidJson(doc, "./json/WriteJSON.json");
}

Document和Value使用默认构造函数构造,类型都是Null,需要SetXXX()或者直接赋值设置类型

输出

{"key1": "string","key2": {"key2.1": 123,"key2.2": true},"key3": 1.23,"key4": [1,2,3,4,5,6,7,8]
}

写(UTF-8)

这个地方和之前读的时候的思路是一样的,既然把Document都换成了以wchar_t为编码单位的WDocument,那么要想对其进行保存,必然要将所有涉及到的类都进行响应修改

typedef rapidjson::GenericStringBuffer<rapidjson::UTF8<wchar_t> > WStringBuffer;
typedef rapidjson::PrettyWriter<WStringBuffer, rapidjson::UTF8<wchar_t>, rapidjson::UTF8<wchar_t>> WPrettyWriter;
bool SaveWRapidJson(WDocument& doc, const char* fileName) {WStringBuffer buffer;WPrettyWriter writer(buffer);doc.Accept(writer);const wchar_t* output = buffer.GetString();OverWriteUTF8(fileName, output);return true;
}

测试用例

在上面读取UTF-8文件的测试用例中你可能已经发现了,最下面有一段代码

//更改HP后之后保存
const char* newName = "./json/Riven-Update.json";
vHp.SetInt(100000);
SaveWRapidJson(doc, newName);

输出

这地方用RapidJson自带的格式化输出方法还是有些诡异,数组全部都给变成单列的了,我暂时没有去找怎么改这个。

{"id": "Riven","key": "92","name": "放逐之刃","title": "锐雯","skins": [1,2,3,4,5],"stats": {"hp": 100000,"mp": 0,"movespeed": 340,"armor": 33,"spellblock": 32.1,"attackrange": 125,"crit": 0,"attackdamage": 64,"attackspeed": 0.625}
}

RapidJson还有很多东西,这里就不一一列举了,查看官方文档是最佳选择。

综合案例

俄罗斯方块大家都玩过,甚至也自己制作过,网上有很多的代码,包括我自己也写过,大多数都是将方块数据在代码中硬编码,这是可行的,但是在现代游戏中,很多游戏数据都是存储在外存,当游戏需要时动态进行加载和卸载。

下面我们就单纯的从JSON文件中,加载俄罗斯方块的方块数据。

准备数据

{"Blocks": [[[0,0,0,0,0,1,0,0,0,1,0,0,0,1,1,0],[0,0,0,0,0,0,0,0,0,0,1,0,1,1,1,0],[0,0,0,0,0,1,1,0,0,0,1,0,0,0,1,0],[0,0,0,0,0,0,0,0,1,1,1,0,1,0,0,0]],[[0,0,0,0,0,0,1,0,0,1,1,0,0,0,1,0],[0,0,0,0,0,0,0,0,1,1,1,0,0,1,0,0],[0,0,0,0,0,1,0,0,0,1,1,0,0,1,0,0],[0,0,0,0,0,0,0,0,0,1,0,0,1,1,1,0]],[[0,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0],[0,0,0,0,0,1,0,0,0,1,1,0,0,0,1,0]],[[0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,0]],[[0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1]]]
}

请注意,在Blocks内部,第一层是形状的数量,第二层是方块的元素,0表示无,1表示有。

方块是4*4的矩阵,JSON文件中使用了一维数组表示二维数组,在程序中是一个三维的数组,存储若干个二维矩阵。为什么不分形状进行存储,这一点以后有时间会说。

过程基本上都在注释里面了,解析的过程可能有点复杂

//4*4方块矩阵
const int BLOCK_LENGTH = 4;void ReadBlocks() {const char* fileName = "./json/Blocks.json";rapidjson::Document doc = GetDocument(fileName);if (!doc.IsObject()) {cout << "Faild to valid JSON";return;}//获取节点rapidjson::Value& vBlocks = doc["Blocks"];//形状数量const int shapeNum = vBlocks.Size();//形状的方块数量int* blockNum = new int[shapeNum];//形状范围int* sumNum = new int[shapeNum];//初始化二维数组for (int i = 0; i < shapeNum; i++) {blockNum[i] = 0;sumNum[i] = 0;}//方块数量int count = 0;for (int i = 0; i < shapeNum; i++) {//每个形状对应的角度数量blockNum[i] = vBlocks[i].Size();sumNum[i] += 0;count += blockNum[i];}//初始化三维数组[13][BLOCK_LENGTH][BLOCK_LENGTH]int*** blocks = new int** [count];for (int i = 0; i < count; i++) {blocks[i] = new int* [BLOCK_LENGTH];for (int j = 0; j < BLOCK_LENGTH; j++) {blocks[i][j] = new int[BLOCK_LENGTH];for (int k = 0; k < BLOCK_LENGTH; k++) {blocks[i][j][k] = 0;}}}//将文件中的数组读取到三维数组int n = 0;for (int i = 0; i < shapeNum; i++) {for (int j = 0; j < blockNum[i]; j++) {rapidjson::Value matrix = vBlocks[i][j].GetArray();for (int k = 0; k < static_cast<int>(matrix.Size()); k++) {int row = static_cast<int>(floor(k / BLOCK_LENGTH));int col = k % BLOCK_LENGTH;blocks[n][row][col] = matrix[k].GetInt();}n++;}}//查看现在三维数组中的数据for (int i = 0; i < count; i++) {for (int j = 0; j < BLOCK_LENGTH; j++) {for (int k = 0; k < BLOCK_LENGTH; k++) {cout << blocks[i][j][k] << " ";}cout << std::endl;}cout << std::endl;}//释放for (int i = 0; i < count; i++) {for (int j = 0; j < BLOCK_LENGTH; j++) {delete[] blocks[i][j];}delete[] blocks[i];}delete[] blocks;delete[] blockNum;delete[] sumNum;
}

输出

0 0 0 0
0 1 0 0
0 1 0 0
0 1 1 00 0 0 0
0 0 0 0
0 0 1 0
1 1 1 00 0 0 0
0 1 1 0
0 0 1 0
0 0 1 00 0 0 0
0 0 0 0
1 1 1 0
1 0 0 00 0 0 0
0 0 1 0
0 1 1 0
0 0 1 00 0 0 0
0 0 0 0
1 1 1 0
0 1 0 00 0 0 0
0 1 0 0
0 1 1 0
0 1 0 00 0 0 0
0 0 0 0
0 1 0 0
1 1 1 00 0 0 0
0 0 0 0
0 1 1 0
1 1 0 00 0 0 0
0 1 0 0
0 1 1 0
0 0 1 00 0 0 0
0 0 0 0
0 1 1 0
0 1 1 00 1 0 0
0 1 0 0
0 1 0 0
0 1 0 00 0 0 0
0 0 0 0
0 0 0 0
1 1 1 1

OK,会使用JSON读写文件之后,基本上已经能搞定很多数据存储问题了。

(三)C++游戏开发-本地存储-JSON文件读写相关推荐

  1. (二)C++游戏开发-本地存储-文本文件读写

    文本文件读写 简介 写(ASCLL) 读(ASCLL) 写(UTF-8) 读(UTF-8) ifstream不能读取UTF-8 其它问题 简介 文件IO属于IO的基础操作,网上也有好多资料,写法挺多的 ...

  2. Ajax怎么获取data为集合的值,vue,ajax获取本地的json文件,赋值后,computed和mounted里访问不到重新赋值后的listDatas...

    ajax获取本地的json文件,取到数据之后赋值给data的listDatas,view页面更新了,但是computed和mounted里访问不到重新赋值后的listDatas,除了在$get()里能 ...

  3. iOS查看手机app本地存储的文件

    审计过程中,经常会检查程序是否在本地存储了一些敏感文件,可以通过以下方法来查看手机上本地存储的文件: 1.选择xcode上面的window下面的Devices 2.先在左边选中你当前的设备,然后在右下 ...

  4. c语言编程抢30,C语言编写抢三十游戏——开发笔记(总结).doc

    C语言编写抢三十游戏--开发笔记(总结) 课程设计题目:抢三十游戏 学号:XXX 姓名:XXX 组员:XXX(组长).XXX.XXX.XXX.XXX.XXX 设计时间:2009-12-19 相关背景: ...

  5. JSON定义及解析,JSON文件读写

    文章目录 JSON 定义 要点 简单的JSON实例 JSON的文档结构 对象 数组 数据类型 值 字符串说明 关于轨道图的嵌套 JSON实例 使用JsonCpp进行JSON文件读写 类图关系 常用接口 ...

  6. 微信小程序入门三: 简易form、本地存储

    实例内容 登陆界面 处理登陆表单数据 处理登陆表单数据(异步) 清除本地数据 实例一: 登陆界面 在app.json中添加登陆页面pages/login/login,并设置为入口. 保存后,自动生成相 ...

  7. 本地服务器json文件,从本地ftp服务器读取Json文件

    我是存储在本地服务器中的名为File1.js的json文件.我想读取json文件的内容,并希望在其他文件中显示数据.我已经尝试使用JavaScript编码,但它不能正常工作.从本地ftp服务器读取Js ...

  8. axios请求本地的json文件在打包部署到子目录域名下,路径找不到

    前言: 因为要同时部署两个项目,有一个是部署到域名下面的子目录下,如:https://xxx.com/siot-admin vue 项目中使用axios请求了本地项目的static文件夹下的json文 ...

  9. java读取本地文件下载_java 读取本地的json文件

    首先,要先去下载相关的jar包,否则你是无法做到的. 在百度或者谷歌里面输入java json  jar包下载就行了(共7个包). xom-1.1.jar ezmorph-1.0.6.jar json ...

最新文章

  1. 你所不知道的Android Studio调试技巧
  2. Hive常见面试问题(持续更新)
  3. 程序观点下的线性代数
  4. struts集成spring官方例子
  5. sql统计表中各类型金额_产品经理市场需求旺盛的10大基础技能——第1篇读透SQL...
  6. video上传架构设计与实现
  7. 29. PHP 错误控制
  8. mysql5.7 keepalived_基于MySQL 5.7多源复制及Keepalived搭建三节点高可用架构
  9. mac清除ps缓存 mac系统存储空间清理
  10. 使用谷歌(Google)TTS服务 – Java版开源gTTS及Python gTTS
  11. 语句摘抄——第13周
  12. 一个故事带你了解集线器、交换机与路由器
  13. 软件开发公司怎么选择比较好?-链环科技
  14. js加ajax实现分页
  15. cocos creator 优化之相机渲染 drawcall优化
  16. Visual Studio 2019编译问题解决方法
  17. 【Dash搭建可视化网站】项目11:世界自杀率数据看板
  18. 计算机专业该如何学习:大一篇
  19. Java中Scanner类的用法
  20. 1 buffers were freed while being dequeued

热门文章

  1. cmd命令基础知识学习笔记
  2. 干货:为什么要学习Mysql?
  3. 未来你家的机器人,很有可能产自这家公司
  4. 计算机高新技术学什么,【全国计算机等级考试】和【全国高新技术办公软件操作员】有什么区别?...
  5. linux7防火墙拒绝ip访问,FirewallD防火墙常用经验——开放端口——拒绝某个IP访问...
  6. CKFinder 介绍
  7. Asp.net core 身份认证框架 Microsoft Identity的使用以及如何使用Idengtity创建自带的用户模型SignInManager和UserManager的使用等等
  8. 【协议】LW_APP蓝牙通讯协议
  9. 让scroll lock指示灯闪烁
  10. 什么是设计模式?常用的设计模式有哪些?