使用purescript-argonaut-codecs处理含枚举与可变子Schema的复杂JSON
我有一个包含可变子JSON的复杂JSON结构,每个子JSON都有独立Schema,结构示例如下:
{ "node_level": "unit", "name": "unit 1", "description": "the first unit", "theme_image_path": "hello.png", "children": [ { "node_level": "chapter", "name": "chapter 1", "theme_image_path": "dog.png", "children": [ { "node_level": "lesson", "lesson_type": "reading", "children": [ { "node_level": "activity", "activity_type": "vocabulary", "elements": { /* 此处结构随activity_type系统变化 */ } } ] } ] } ] }
注意:activity层级对应多种不同的对象Schema——每种activity_type对应专属Schema,activity_type和elements字段会随之变化。
我当前基于purescript-argonaut-codecs库的实现无法正常运行(报错见下文),也未达成部分目标,具体实现代码如下:
import Data.Argonaut.Encode (encodeJson) -- 当前代码报错 contentToJson :: Content -> Json contentToJson = encodeJson type UnitNode = { node_level :: String -- 希望强制该值为"unit"(见问题3) , name :: String , description :: String , theme_image_path :: String , children :: Array ChapterNode } type ChapterNode = { node_level :: String -- 希望强制该值为"chapter"(见问题3) , name :: String , theme_image_path :: String , children :: Array LessonNode } type LessonNode = { node_level :: String -- 希望强制该值为"lesson"(见问题3) , lesson_type :: String -- 希望实现枚举类形式(见问题1) , children :: Array ActivityNode } type ActivityNode = { node_level :: String -- 希望强制该值为"activity"(见问题3) , layout :: String -- 希望实现枚举类形式(见问题1) , elements :: VocabFlashcard -- 此处应支持多种类型(见问题2) } type VocabFlashcard = { audio :: String , image :: String , vocabText :: String , translation :: String }
我在PureScript中遇到以下三个难题,这些在TypeScript中均可实现,若无法解决将考虑转回TypeScript:
1. 枚举类字段的类型约束与编解码器问题
部分字段(node_level、lesson_type、activity_type)只能取特定闭合字符串子集,比如node_level仅能为"unit"、"chapter"、"lesson"或"activity"。在TypeScript中我会用type NodeLevel = "unit" | "chapter" | "activity" | "lesson"实现,但在PureScript中尝试两种方法均遇问题:
- 方法①:使用
newtype:无编解码器时可正常工作,但编解码器库报错:
No type class instance was found for Data.Argonaut.Encode.Class.EncodeJson LessonType while solving type class constraint Data.Argonaut.Encode.Class.GEncodeJson ( children :: Array { elements :: { audio :: String , image :: String , translation :: String , vocabText :: String } , layout :: String , node_level :: String } , lesson_type :: LessonType , node_level :: String ) (Cons @Type "lesson_type" LessonType (Cons @Type "node_level" String (Nil @Type))) while checking that type forall (@a :: Type). EncodeJson a => a -> Json is at least as general as type Array { children :: Array ... , description :: String , name :: String , node_level :: String , theme_image_path :: String } -> Json while checking that expression encodeJson has type Array { children :: Array ... , description :: String , name :: String , node_level :: String , theme_image_path :: String } -> Json in value declaration contentToJson
- 方法②:使用枚举类
data类型:虽可行但代码过于冗长,且同样遇到编解码器报错:No type class instance was found for Data.Argonaut.Encode.Class.EncodeJson NodeLevel ...
2. 可变子Schema的联合类型实现问题
在TypeScript中我会用联合类型实现(示例代码如下),确保Activity数组符合特定Schema。但PureScript似乎不支持用|运算符定义多类型联合,是否应使用data类型?若如此又会遇到上述编解码器问题。
type Lesson = { node_level: NodeLevel; children: Activity[]; lesson_type: LessonType; } type ActivityName = "vocab" | "grammar" | (... etc) type Activity = Vocabulary | Grammar | (... etc) type Vocabulary = { node_level: NodeLevel, activity_type: ActivityName, elements: { audio: string, image: string, vocabText: string, translation: string }, } -- 其他Activity类型定义...
3. 强制固定字段值的类型约束问题
理想情况下,JSON中node_level字段的值需根据层级子Schema强制设定,比如Vocabulary的node_level需固定为"activity"。
内容的提问来源于stack exchange,提问作者stasp

