零基础鸿蒙应用开发第三十二节:JSON核心基础与文件的读写
【学习目标】
- 掌握 JSON 的核心结构、数据类型与语法规则,能独立编写合法的 JSON 数据;
- 熟练运用 ArkTS 内置
JSON模块,实现对象与 JSON 字符串的序列化/反序列化; - 掌握鸿蒙应用中本地 JSON 文件的创建、存放与读写流程(包括只读的
rawfile和可读写的沙箱目录); - 规避 JSON 处理中的常见错误,提升数据处理稳定性,为后续 JSON 模拟商品数据打基础。
【学习重点】
JSON 作为轻量级数据交换格式,是鸿蒙开发中接口交互、配置存储的核心工具。本节以本地 JSON 文件操作为主线,从基础格式到实战流程,掌握创建 JSON 文件→读取内容→反序列化为对象→修改数据→序列化保存的完整链路。
一、工程目录结构
本节我们将创建名为JsonDemo的工程,基于鸿蒙5.0(API12)开发,使用DevEco Studio 6.0+工具,项目结构目录如下:
entry/
├── src/
│ ├── main/
│ │ ├── ets/
│ │ │ ├── model/ // 数据模型(强类型接口,按业务拆分)
│ │ │ │ ├── UserBasic.ets // 简单用户信息接口
│ │ │ │ ├── UserHobbies.ets // 用户详情(含联系方式、地址)接口
│ │ │ │ ├── OrderDetail.ets // 订单相关接口
│ │ │ │ └── UserConfig.ets // 用户配置信息接口
│ │ │ ├── pages/ // 页面组件
│ │ │ │ └── Index.ets // 主页面(JSON读取、解析、展示)
│ │ │ └── utils/ // 工具类目录
│ │ │ └── JsonUtil.ets // JSON文件读写工具类(rawfile+沙箱)
│ │ ├── resources/ // 应用资源目录
│ │ │ └── rawfile/ // 静态JSON文件目录(手动创建)
│ │ │ ├── user_basic.json // 简单用户信息JSON
│ │ │ ├── user_with_hobbies.json // 中等复杂度用户JSON
│ │ │ └── order_detail.json // 复杂订单JSON
│ │ └── module.json5 // 应用配置文件(可选:添加存储权限)
│ └── oh-package.json5 // 项目依赖配置
└── build-profile.json5 // 工程构建配置
二、JSON 核心基础
1. 核心结构与语法规则
- 对象:
{}包裹的键值对集合,键名必须用双引号(如{"name": "张三"}) - 数组:
[]包裹的有序值集合(如["苹果", "香蕉"]) - 数据类型:字符串(双引号)、数字、布尔值(
true/false)、null、对象、数组 - 语法禁忌:无注释、无尾逗号、无单引号、无
undefined/函数(这些都会导致 JSON 解析失败)
三、JSON 示例
1. 简单示例:用户基本信息
创建步骤:选中 src/main/resources/rawfile 目录 → 右击 → NEW → File → 命名为 user_basic.json
文件路径:entry/src/main/resources/rawfile/user_basic.json
{
"id": 1001,
"name": "张三",
"age": 25,
"isVip": false
}
2. 中等示例:用户信息嵌套
文件路径:entry/src/main/resources/rawfile/user_with_hobbies.json
{
"id": 1002,
"name": "李四",
"contact": {
"phone": "13800138000",
"email": "lisi@example.com"
},
"hobbies": ["跑步", "摄影", "编程"],
"addresses": [
{"type": "home", "city": "北京"},
{"type": "work", "city": "上海"}
]
}
3. 复杂示例:商品订单数据
文件路径:entry/src/main/resources/rawfile/order_detail.json
{
"orderId": "ORD20240601001",
"createTime": "2024-06-01 09:30:00",
"user": {
"id": 1003,
"name": "王五",
"memberLevel": "gold"
},
"products": [
{
"productId": "P001",
"name": "智能手表",
"price": 1299.00,
"quantity": 1,
"attrs": {"color": "黑色", "version": "42mm"}
},
{
"productId": "P002",
"name": "充电线",
"price": 59.00,
"quantity": 2,
"attrs": {"length": "1.5m", "type": "Type-C"}
}
],
"payment": {
"amount": 1417.00,
"method": "alipay",
"status": "paid",
"paidTime": "2024-06-01 09:32:15"
}
}
四、实战操作
1. 准备工作
- 文件存放目录
目录类型 路径 权限 适用场景 只读目录 src/main/resources/rawfile仅读取,编译后打包进应用 静态配置、模板数据 可读写目录 应用沙箱 cache/files目录可读可写,应用卸载后删除 用户配置、本地缓存数据 - 核心 API
resourceManager.getRawFileContent():读取rawfile目录文件(返回ArrayBuffer)fs模块:操作沙箱目录文件(读取/写入/删除)JSON.parse():反序列化(JSON 字符串 → ArkTS 对象)JSON.stringify():序列化(对象 → JSON 字符串,支持格式化缩进)
2. 封装 JSON 工具类
在 main/ets/utils 目录下创建 JsonUtil.ets,封装通用读写函数,重点处理异常和文件句柄关闭:
// main/ets/utils/JsonUtil.ets
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';
import { Context } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
export class JsonUtil {
/**
* 读取 rawfile 目录下的 JSON 文件并返回字符串内容
* @param context 应用/组件上下文
* @param fileName JSON 文件名(支持子目录如 "user/user_info.json")
* @returns JSON 字符串
*/
static async readRawFileJson(context: Context, fileName: string): Promise<string> {
try {
const buffer = await context.resourceManager.getRawFileContent(fileName);
const uint8Array = new Uint8Array(buffer);
const decoder = new util.TextDecoder();
return decoder.decodeToString(uint8Array);
} catch (err) {
const error = err as BusinessError;
const msg = `读取JSON文件 ${fileName} 失败:${error.code} - ${error.message}`;
console.error(msg);
throw new Error(msg);
}
}
/**
* 写入JSON数据到应用沙箱的cache目录(可读写)
* @param context 应用上下文
* @param fileName 文件名(如 "user_config.json")
* @param data 要序列化的对象
*/
static async writeSandboxJson(context: Context, fileName: string, data: object): Promise<void> {
let file: fs.File | undefined = undefined;
try {
const cacheDir = context.cacheDir;
const filePath = `${cacheDir}/${fileName}`;
const jsonStr = JSON.stringify(data, null, 2);
// 打开/创建文件
file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
// 写入数据
fs.writeSync(file.fd, jsonStr);
console.log(`JSON数据已写入沙箱:${filePath}`);
} catch (err) {
const error = err as BusinessError;
const msg = `写入沙箱JSON文件 ${fileName} 失败:${error.code} - ${error.message}`;
console.error(msg);
throw new Error(msg);
} finally {
// 无论是否异常,都要关闭文件句柄,避免泄漏
if (file) {
fs.closeSync(file);
}
}
}
/**
* 从应用沙箱的cache目录读取JSON文件
* @param context 应用上下文
* @param fileName 文件名
* @returns JSON 字符串
*/
static async readSandboxJson(context: Context, fileName: string): Promise<string> {
let file: fs.File | undefined = undefined;
try {
const cacheDir = context.cacheDir;
const filePath = `${cacheDir}/${fileName}`;
// 1. 检查文件是否存在
fs.accessSync(filePath);
// 2. 打开文件(只读模式)
file = fs.openSync(filePath, fs.OpenMode.READ_ONLY);
// 3. 获取文件大小
const fileStat = fs.statSync(filePath);
// 4. 创建缓冲区读取数据
const buffer = new ArrayBuffer(fileStat.size);
const readBytes = fs.readSync(file.fd, buffer, { length: fileStat.size });
// 5. 校验读取完整性
if (readBytes !== fileStat.size) {
throw new Error(`文件读取不完整:预期${fileStat.size}字节,实际${readBytes}字节`);
}
// 6. 解码为字符串
const decoder = new util.TextDecoder();
return decoder.decodeToString(new Uint8Array(buffer));
} catch (err) {
const error = err as BusinessError;
const msg = `读取沙箱JSON文件 ${fileName} 失败:${error?.code} - ${error?.message || err}`;
console.error(msg);
throw new Error(msg);
} finally {
if (file) {
fs.closeSync(file);
}
}
}
/**
* 类型安全的JSON反序列化
* @param jsonStr JSON字符串
* @returns 指定类型的对象
*/
static parseJson<T>(jsonStr: string): T {
try {
return JSON.parse(jsonStr) as T;
} catch (err) {
const msg = `JSON反序列化失败:${(err as Error).message}`;
console.error(msg);
throw new Error(msg);
}
}
}
3. 创建与JSON结构匹配的强类型接口
3.1 简单用户信息接口(UserBasic.ets)
// main/ets/model/UserBasic.ets
/**
* 简单用户信息接口(对应 user_basic.json)
*/
export interface UserBasic {
id: number;
name: string;
age: number;
isVip: boolean;
}
3.2 用户详情接口(UserHobbies.ets)
// main/ets/model/UserHobbies.ets
/**
* 联系方式接口
*/
export interface Contact {
phone: string;
email: string;
}
/**
* 地址接口
*/
export interface Address {
type: string;
city: string;
}
/**
* 用户详情接口(对应 user_with_hobbies.json)
*/
export interface UserDetail {
id: number;
name: string;
contact: Contact;
hobbies: string[];
addresses: Address[];
}
3.3 订单相关接口(OrderDetail.ets)
// main/ets/model/OrderDetail.ets
/**
* 订单用户信息接口
*/
export interface OrderUser {
id: number;
name: string;
memberLevel: string;
}
/**
* 商品属性接口(适配不同商品的动态属性)
*/
export interface ProductAttr {
length?: string;
type?: string;
color?: string;
version?: string;
}
/**
* 订单商品接口
*/
export interface OrderProduct {
productId: string;
name: string;
price: number;
quantity: number;
attrs: ProductAttr;
}
/**
* 订单支付信息接口
*/
export interface OrderPayment {
amount: number;
method: string;
status: string;
paidTime: string;
}
/**
* 订单详情接口(对应 order_detail.json)
*/
export interface OrderDetail {
orderId: string;
createTime: string;
user: OrderUser;
products: OrderProduct[];
payment: OrderPayment;
}
3.4 用户配置接口(UserConfig.ets)
// main/ets/model/UserConfig.ets
/**
* 用户配置信息接口(对应沙箱存储的配置数据)
*/
export interface UserConfig {
theme: string;
fontSize: number;
autoSave: boolean;
lastLoginTime: string;
}
4. 页面实战:读取、解析、修改 JSON 数据
在 main/ets/pages/Index.ets 中实现业务逻辑,修正方法调用错误,保证异步执行顺序:
// main/ets/pages/Index.ets
import { JsonUtil } from '../utils/JsonUtil';
import { OrderDetail } from '../model/OrderDetail';
import { Context } from '@kit.AbilityKit';
import { UserDetail } from '../model/UserHobbies';
import { UserConfig } from '../model/UserConfig';
@Entry
@Component
struct Index {
// 获取组件上下文
private context: Context = this.getUIContext().getHostContext() as Context;
/**
* 页面即将显示时执行
*/
aboutToAppear(): void {
// 按顺序执行异步任务,避免日志输出混乱
this.executeAllParseTasks();
}
/**
* 顺序执行所有解析任务
*/
async executeAllParseTasks(): Promise<void> {
await this.parseUserWithHobbies();
await this.parseOrderDetail();
await this.testSandboxJsonOperation();
}
/**
* 解析 user_with_hobbies.json 数据
*/
async parseUserWithHobbies(): Promise<void> {
try {
const jsonContent = await JsonUtil.readRawFileJson(this.context, "user_with_hobbies.json");
const user: UserDetail = JsonUtil.parseJson<UserDetail>(jsonContent);
// 打印原始数据
console.log("\n【用户详情解析结果】:");
console.log(`用户 ${user.name}(ID: ${user.id})`);
console.log(`爱好:${user.hobbies.join('、')}`);
// 修改数据并序列化
user.name = "李四_修改后";
user.hobbies.push("钓鱼");
const modifiedJson = JSON.stringify(user, null, 2);
console.log("\n【修改后的用户JSON】:", modifiedJson);
} catch (err) {
console.error("解析用户详情失败:", err);
}
}
/**
* 解析 order_detail.json 数据(强类型)
*/
async parseOrderDetail(): Promise<void> {
try {
const jsonContent = await JsonUtil.readRawFileJson(this.context, "order_detail.json");
const order: OrderDetail = JsonUtil.parseJson<OrderDetail>(jsonContent);
console.log("\n【订单数据解析结果】:");
console.log(`订单号:${order.orderId},总金额:${order.payment.amount} 元`);
order.products.forEach((product, index) => {
console.log(` ${index + 1}. ${product.name} ×${product.quantity}`);
console.log(` 属性:${JSON.stringify(product.attrs)}`);
});
} catch (err) {
console.error("解析订单数据失败:", err);
}
}
/**
* 测试沙箱目录JSON读写(存储用户配置)
*/
async testSandboxJsonOperation(): Promise<void> {
try {
// 1. 定义用户配置数据
const userConfig: UserConfig = {
theme: "dark",
fontSize: 16,
autoSave: true,
lastLoginTime: new Date().toISOString()
};
// 2. 写入沙箱
await JsonUtil.writeSandboxJson(this.context, "user_config.json", userConfig);
// 3. 读取沙箱
const jsonStr = await JsonUtil.readSandboxJson(this.context, "user_config.json");
const config: UserConfig = JsonUtil.parseJson<UserConfig>(jsonStr);
console.log("\n【沙箱读取的配置】:", config);
} catch (err) {
console.error("沙箱JSON操作失败:", err);
}
}
/**
* 构建 UI 页面
*/
build() {
Column() {
Text("JSON 数据处理实战")
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin(20);
Text("请打开控制台查看日志输出(Logcat)")
.fontSize(16)
.fontColor(Color.Grey);
Button("重新写入用户配置")
.margin(10)
.onClick(async () => {
await this.testSandboxJsonOperation();
});
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center);
}
}
5. 运行程序
- 在
entry/src/main/resources下rawfile目录,放入 3 个 JSON 文件; - 配置权限(可选):若使用沙箱
files目录(非cache),在module.json5中添加权限:{ "module": { "requestPermissions": [ { "name": "ohos.permission.READ_USER_STORAGE" }, { "name": "ohos.permission.WRITE_USER_STORAGE" } ] } }注意:沙箱
cache目录无需申请上述权限。 - 运行方式:必须使用真机或模拟器,预览模式不支持
resourceManager和fs接口; - 查看日志:在 DevEco Studio 的 Logcat 面板查看输出,正确日志示例:
【用户详情解析结果】: 用户 李四(ID: 1002) 爱好:跑步、摄影、编程 【修改后的用户JSON】: { "id": 1002, "name": "李四_修改后", "contact": { "phone": "13800138000", "email": "lisi@example.com" }, "hobbies": ["跑步","摄影","编程","钓鱼"], "addresses": [{"type":"home","city":"北京"},{"type":"work","city":"上海"}] } 【订单数据解析结果】: 订单号:ORD20240601001,总金额:1417 元 1. 智能手表 ×1 属性:{"color":"黑色","version":"42mm"} 2. 充电线 ×2 属性:{"length":"1.5m","type":"Type-C"} JSON数据已写入沙箱:/data/storage/el2/base/haps/entry/cache/user_config.json 【沙箱读取的配置】: {"theme":"dark","fontSize":16,"autoSave":true,"lastLoginTime":"2024-06-01T09:30:00.000Z"} - 查看沙箱文件:打开"Device File Browser"在/data/storage/el2/base/haps/entry/cache查看写入的文件'user_config.json'
五、常见错误与避坑指南
| 错误类型 | 问题表现 | 解决方案 |
|---|---|---|
| 预览器环境限制 | 读取文件失败,提示resourceManager未定义 |
改用真机/模拟器运行,预览模式仅用于UI调试 |
| 文件路径错误 | 提示文件不存在,如user.json写成User.json |
路径严格区分大小写,使用rawfile相对路径(如user_with_hobbies.json) |
| 上下文获取错误 | context为undefined |
在UI组件中通过this.getUIContext().getHostContext() as Context获取 |
| 接口与JSON结构不匹配 | 解析后属性为undefined |
用 JSON.cn 格式化JSON,确保接口字段名、类型完全一致 |
| 异步处理不当 | 日志输出顺序混乱,数据未解析完成就使用 | 异步函数用await,多个任务封装到一个async函数中按顺序执行 |
| JSON语法错误 | 解析失败,提示Unexpected token |
避免单引号、尾逗号、注释,用 JSON.cn 校验合法性 |
| 文件句柄泄漏 | 多次写入后提示文件被占用 | 所有fs.openSync操作必须在finally块中调用fs.closeSync |
六、内容总结
- JSON 核心:语法严格(键双引号、无注释/尾逗号),支持6种数据类型,不支持函数、
undefined; - 鸿蒙 JSON 存储双场景
- 只读场景:
resourceManager读取rawfile静态JSON,适用于配置模板; - 可读写场景:
fs模块操作沙箱cache/files目录,适用于动态用户配置;
- 只读场景:
- 工程化要点
- JSON 操作封装为工具类,统一处理异常和文件句柄;
- 用强类型接口匹配 JSON 结构,提升代码可读性和安全性;
- 异步任务按顺序执行,避免数据竞争和日志混乱;
- 避坑核心:拒绝预览器运行、严格匹配接口与 JSON 结构、保证文件句柄关闭、正确使用
await处理异步。
七、代码仓库
- 工程名称:
JsonDemo - 代码地址:https://gitee.com/juhetianxia321/harmony-os-code-base.git
八、下节预告
本节我们掌握了 JSON 数据的序列化/反序列化,以及鸿蒙应用中rawfile和沙箱目录的文件读写能力,为后续 JSON 模拟商品数据做好了准备。
但实际开发中,JSON 数据的字段合法性校验是必不可少的环节——比如商品价格必须为正数、用户手机号格式必须正确、订单日期必须符合规范。直接使用不合规数据,极易引发业务逻辑错误。
下一节,我们将聚焦 正则表达式的基础与实战应用,重点学习:
- 正则表达式的核心语法(元字符、量词、边界匹配),构建常见场景的匹配模式;
- ArkTS 中
RegExp类的核心方法,实现字符串的校验、提取与替换; - 结合 JSON 数据处理场景,用正则完成手机号、邮箱、价格等字段的合法性验证;
- 正则使用中的常见坑点与避坑技巧,保证校验逻辑的准确性。
通过下一节的学习,你将掌握字符串处理的强大工具,与 JSON 处理能力结合,形成 “数据读取→格式校验→业务处理” 的完整链路。
浙公网安备 33010602011771号