Rescript解析package.json为类型化数据时遇类型错误求助
Rescript解析package.json的类型错误解决方法
问题核心
尝试将package.json解析为自定义packageData类型时,访问result.scripts[0].name触发类型错误,提示option<script>与packageData类型不匹配。本质是自定义类型与JSON实际结构不匹配,导致解析后的数据不符合预期。
错误原因
- scripts字段类型不匹配:package.json里的
scripts是键值对对象({ "res:build": "rescript", ... }),但代码中定义为array<script>数组,JSON解析后得到的是JS对象而非数组,直接用索引[0]访问会返回undefined,Rescript将其推断为option<script>,与预期的数组元素类型冲突。 - dependencies字段类型不匹配:同理,
dependencies也是键值对对象,代码中定义成array<(string, string)>数组,不符合实际结构。 - author字段定义与数据不符:package.json里的
author是空字符串,而代码中定义为option<string>,空字符串并非Rescript的None,会导致类型断言失败。 - 不安全的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
相关产品推荐
相关产品推荐

