零基础鸿蒙应用开发第三十二节:JSON核心基础与文件的读写

零基础鸿蒙应用开发学习计划表

【学习目标】

  1. 掌握 JSON 的核心结构、数据类型与语法规则,能独立编写合法的 JSON 数据;
  2. 熟练运用 ArkTS 内置 JSON 模块,实现对象与 JSON 字符串的序列化/反序列化;
  3. 掌握鸿蒙应用中本地 JSON 文件的创建、存放与读写流程(包括只读的rawfile和可读写的沙箱目录);
  4. 规避 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 目录 → 右击 → NEWFile → 命名为 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. 运行程序

  1. entry/src/main/resourcesrawfile 目录,放入 3 个 JSON 文件;
  2. 配置权限(可选):若使用沙箱files目录(非cache),在module.json5中添加权限:
    {
      "module": {
        "requestPermissions": [
          { "name": "ohos.permission.READ_USER_STORAGE" },
          { "name": "ohos.permission.WRITE_USER_STORAGE" }
        ]
      }
    }
    

    注意:沙箱cache目录无需申请上述权限。

  3. 运行方式必须使用真机或模拟器,预览模式不支持 resourceManagerfs 接口;
  4. 查看日志:在 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"}
    
  5. 查看沙箱文件:打开"Device File Browser"在/data/storage/el2/base/haps/entry/cache查看写入的文件'user_config.json'

五、常见错误与避坑指南

错误类型 问题表现 解决方案
预览器环境限制 读取文件失败,提示resourceManager未定义 改用真机/模拟器运行,预览模式仅用于UI调试
文件路径错误 提示文件不存在,如user.json写成User.json 路径严格区分大小写,使用rawfile相对路径(如user_with_hobbies.json
上下文获取错误 contextundefined 在UI组件中通过this.getUIContext().getHostContext() as Context获取
接口与JSON结构不匹配 解析后属性为undefined JSON.cn 格式化JSON,确保接口字段名、类型完全一致
异步处理不当 日志输出顺序混乱,数据未解析完成就使用 异步函数用await,多个任务封装到一个async函数中按顺序执行
JSON语法错误 解析失败,提示Unexpected token 避免单引号、尾逗号、注释,用 JSON.cn 校验合法性
文件句柄泄漏 多次写入后提示文件被占用 所有fs.openSync操作必须在finally块中调用fs.closeSync

六、内容总结

  1. JSON 核心:语法严格(键双引号、无注释/尾逗号),支持6种数据类型,不支持函数、undefined
  2. 鸿蒙 JSON 存储双场景
    • 只读场景:resourceManager读取rawfile静态JSON,适用于配置模板;
    • 可读写场景:fs模块操作沙箱cache/files目录,适用于动态用户配置;
  3. 工程化要点
    • JSON 操作封装为工具类,统一处理异常和文件句柄;
    • 用强类型接口匹配 JSON 结构,提升代码可读性和安全性;
    • 异步任务按顺序执行,避免数据竞争和日志混乱;
  4. 避坑核心:拒绝预览器运行、严格匹配接口与 JSON 结构、保证文件句柄关闭、正确使用await处理异步。

七、代码仓库

八、下节预告

本节我们掌握了 JSON 数据的序列化/反序列化,以及鸿蒙应用中rawfile和沙箱目录的文件读写能力,为后续 JSON 模拟商品数据做好了准备。

但实际开发中,JSON 数据的字段合法性校验是必不可少的环节——比如商品价格必须为正数、用户手机号格式必须正确、订单日期必须符合规范。直接使用不合规数据,极易引发业务逻辑错误。

下一节,我们将聚焦 正则表达式的基础与实战应用,重点学习:

  1. 正则表达式的核心语法(元字符、量词、边界匹配),构建常见场景的匹配模式;
  2. ArkTS 中RegExp类的核心方法,实现字符串的校验、提取与替换;
  3. 结合 JSON 数据处理场景,用正则完成手机号、邮箱、价格等字段的合法性验证;
  4. 正则使用中的常见坑点与避坑技巧,保证校验逻辑的准确性。

通过下一节的学习,你将掌握字符串处理的强大工具,与 JSON 处理能力结合,形成 “数据读取→格式校验→业务处理” 的完整链路。

posted @ 2026-01-25 12:11  鸿蒙-散修  阅读(0)  评论(0)    收藏  举报