You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rescript解析package.json为类型化数据时遇类型错误求助

Rescript解析package.json的类型错误解决方法

问题核心

尝试将package.json解析为自定义packageData类型时,访问result.scripts[0].name触发类型错误,提示option<script>与packageData类型不匹配。本质是自定义类型与JSON实际结构不匹配,导致解析后的数据不符合预期。

错误原因

  1. scripts字段类型不匹配:package.json里的scripts是键值对对象({ "res:build": "rescript", ... }),但代码中定义为array<script>数组,JSON解析后得到的是JS对象而非数组,直接用索引[0]访问会返回undefined,Rescript将其推断为option<script>,与预期的数组元素类型冲突。
  2. dependencies字段类型不匹配:同理,dependencies也是键值对对象,代码中定义成array<(string, string)>数组,不符合实际结构。
  3. author字段定义与数据不符:package.json里的author是空字符串,而代码中定义为option<string>,空字符串并非Rescript的None,会导致类型断言失败。
  4. 不安全的JSON解析绑定:直接绑定JSON.parse为返回packageData的函数属于强制类型断言,忽略了JSON结构与自定义类型的差异,隐藏了实际的类型不匹配问题。

解决方案

步骤1:修正类型定义

根据package.json的实际结构,调整packageData的类型:

// 使用Js.Dict.t<string>表示键值对对象,更符合JSON结构
type packageData = {
  name: string,
  version: string,
  scripts: Js.Dict.t<string>,
  keywords: array<string>,
  author: string, // 若需空字符串转为None,后续可手动处理
  license: string,
  dependencies: Js.Dict.t<string>,
}

步骤2:安全解析JSON

避免直接强制断言类型,使用Rescript自带的JSON解析工具Js.Json和Belt.Option处理解析过程:

@module("fs")
external readFileSync: (~name: string, [#utf8]) => string = "readFileSync"

let file = readFileSync(~name="package.json", #utf8)
Console.log("File: " ++ file);

// 解析JSON并提取字段
let json = Js.Json.parseExn(file)

let name = json->Js.Json.get("name")->Js.Json.decodeString->Belt.Option.getExn
let version = json->Js.Json.get("version")->Js.Json.decodeString->Belt.Option.getExn
let scripts = json->Js.Json.get("scripts")->Js.Json.decodeObject->Belt.Option.getExn
let keywords = 
  json->Js.Json.get("keywords")->Js.Json.decodeArray->Belt.Option.getExn
  ->Belt.Array.map(item => item->Js.Json.decodeString->Belt.Option.getExn)
let author = json->Js.Json.get("author")->Js.Json.decodeString->Belt.Option.getExn
// 可选:将空字符串转为None
// let author = switch authorRaw {
// | "" => None
// | val => Some(val)
// }
let license = json->Js.Json.get("license")->Js.Json.decodeString->Belt.Option.getExn
let dependencies = json->Js.Json.get("dependencies")->Js.Json.decodeObject->Belt.Option.getExn

let result: packageData = {
  name,
  version,
  scripts,
  keywords,
  author,
  license,
  dependencies,
}

步骤3:正确访问scripts数据

如果需要按数组方式访问scripts,可将Js.Dict转为数组:

// 将scripts对象转为{name: string, value: string}数组
let scriptsArray = scripts->Js.Dict.entries->Belt.Array.map((name, value) => {name, value})

// 安全访问第一个元素(处理数组为空的情况)
switch scriptsArray->Belt.Array.get(0) {
| Some(script) => Console.log("First script: " ++ script.name)
| None => Console.log("No scripts found")
}

如果直接访问特定脚本:

// 获取"res:build"脚本命令
switch Js.Dict.get(result.scripts, "res:build") {
| Some(cmd) => Console.log("res:build command: " ++ cmd)
| None => Console.log("res:build script not found")
}

修正后的完整代码

@module("fs")
external readFileSync: (~name: string, [#utf8]) => string = "readFileSync"

type packageData = {
  name: string,
  version: string,
  scripts: Js.Dict.t<string>,
  keywords: array<string>,
  author: string,
  license: string,
  dependencies: Js.Dict.t<string>,
}

let file = readFileSync(~name="package.json", #utf8)
Console.log("File: " ++ file);

let json = Js.Json.parseExn(file)

let name = json->Js.Json.get("name")->Js.Json.decodeString->Belt.Option.getExn
let version = json->Js.Json.get("version")->Js.Json.decodeString->Belt.Option.getExn
let scripts = json->Js.Json.get("scripts")->Js.Json.decodeObject->Belt.Option.getExn
let keywords = 
  json->Js.Json.get("keywords")->Js.Json.decodeArray->Belt.Option.getExn
  ->Belt.Array.map(item => item->Js.Json.decodeString->Belt.Option.getExn)
let author = json->Js.Json.get("author")->Js.Json.decodeString->Belt.Option.getExn
let license = json->Js.Json.get("license")->Js.Json.decodeString->Belt.Option.getExn
let dependencies = json->Js.Json.get("dependencies")->Js.Json.decodeObject->Belt.Option.getExn

let result: packageData = {
  name,
  version,
  scripts,
  keywords,
  author,
  license,
  dependencies,
}

// 示例:转为数组后访问第一个脚本
let scriptsArray = scripts->Js.Dict.entries->Belt.Array.map((name, value) => {name, value})
switch scriptsArray->Belt.Array.get(0) {
| Some(script) => Console.log("First script: " ++ script.name)
| None => Console.log("No scripts found")
}

// 示例:直接访问指定脚本
switch Js.Dict.get(result.scripts, "res:build") {
| Some(cmd) => Console.log("res:build command: " ++ cmd)
| None => Console.log("res:build script not found")
}

内容的提问来源于stack exchange,提问作者mtyson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:05:21