资讯详情

资讯详情

从JSON到DSH:WorkBuddy数据转换的Python自动化实践

做任务管理的人常有这种经历录入的时候很爽等要把数据交给另一套系统时发现格式完全对不上。我手上的 WorkBuddy 数据就是这个状态。WorkBuddy 是本地优先的待办记录工具导出的 JSON 保留完整字段但下游看板流水线认的是 DSHDelimited Structured Header格式——一种按块组织的键值文本协议。为了不再每次手动复制几百行我写了 workbuddy-to-dsh一条命令把 WorkBuddy 的 JSON 转换成 DSH同时处理日期格式化、字段映射、空值过滤和增量导出。这篇文章适合三类人日常用任务管理工具但需要对接自动化的开发者维护数据流水线的运维同学以及正在学 Python 脚本、想找一个真实练手项目的初学者。下面我从为什么要写这个工具开始把配置、命令、踩过的坑一次说清。1. 为什么要写这样一个转换工具1.1 任务记录与脚本格式之间的落差WorkBuddy 的数据不是一张干净的 CSV 表格而是一个嵌套 JSON。比如一条任务里面可能有标题、状态、优先级、截止时间、标签数组、备注、指派对象、创建时间、修改时间这些字段是给人类阅读和编辑器用的。下游的 DSH 解析器不会去理解任务的业务字段它只按固定语法把文本拆成块凡是符合key: value的行都当作属性处理。要让两端对话本质上要做一次“嵌套对象转扁平文本”的适配。这种适配看起来不难难在稳定。手工转换时你可能今天把时间写成2026-03-31 18:00明天写成2026/03/31 18:00后天带上了GMT。下游解析器一旦遇到不认识的日期整条任务可能被丢弃而不是报错提醒你。类似问题还有很多空值到底输出还是不输出数组到底用逗号还是分号任务标题里的冒号会不会把字段行搞乱。这些零散细节在数据结构只有几条时无所谓当记录数量涨到几百条、而且要每天自动跑时就必须用一套固定规则来兜底。1.2 手工转换的痛点到底在哪我最早也试过人工维护中间文件。做法是打开 WorkBuddy 导出 JSON筛选需要的字段再粘贴到 DSH 模板里。前五十条还好越到后面越容易出问题。第一是复制错位看错一行导致整条任务用了别人的截止时间第二是格式不统一标签在 JSON 里是数组在 DSH 里可能需要逗号连接手工容易漏第三是增量处理非常痛苦每次同步都要猜哪些任务改过只能把整个 DSH 文件推倒重来第四是人工操作不可审计出了问题说不清是数据源的问题还是转换过程的问题。这些痛点总结成一句话任务数量小的时候人工是成本最低的方案一旦超过某个量级人工反而变成最贵的方案。所以我决定写一个命令行转换器把“数据源解析、字段映射、文本生成、增量判断”这些环节固定下来。人工只需要在开始时维护好映射配置之后每次跑命令都能得到一致的结果。1.3 为什么选命令行而不是图形界面这里有个很容易被忽略的选型理由。图形界面适合交互式修改单条记录但自动化任务、定时同步、版本管理都需要“无人工参与”的执行方式。命令行工具天然能进流水线能加参数能写进计划任务输出还能直接重定向到文件。更重要的是命令行工具的所有规则都写在配置和源码里换一个人换一台机器跑出来的结果是一样的。另外命令行工具方便写测试。我把转换规则拆成纯函数给定输入 JSON断言输出的 DSH 文本跑一轮测试用例大概几十毫秒。这个优势在后续修改字段映射时特别明显改坏一个规则测试立刻亮红而不是等到同步任务时被看板消费者发现。对很多人来说脚本只要“能跑”就算完但工具要长期维护测试和可重复性比第一次跑通更重要。2. 先看懂两端的数据格式2.1 WorkBuddy 导出 JSON 长什么样WorkBuddy 的导出文件我习惯放在~/.workbuddy/export.json。结构很简单最外层一个版本号然后是一个 entries 数组{ version: 1, entries: [ { id: wb_001, title: 整理季度复盘材料, status: completed, priority: 1, due: 2026-03-31 18:00, project: analysis, labels: [review, ppt, report], notes: 把各类指标导成图表核对上季度数字。, assignee: zhangsan, createdAt: 2026-03-01T10:00:00Z, updatedAt: 2026-03-29T11:30:00Z } ] }可以看到due是本地时间字符串没有时区信息labels是数组priority是数字而不是人类可读的 P0/P1status是completed/in_progress/todo这类工具内部枚举。这些细节决定了转换脚本不能只做字符串拼接必须针对每种类型写处理规则。还有一个容易忽略的地方是assignee字段可能不存在有些任务没指派负责人脚本需要补一个默认值而不是输出assignee:这种空字段。2.2 DSH 格式的约定DSH 不是通用标准我在文章里指的是团队内部使用的一种文本协议以#开头是注释以task idxxx开头是一个任务块的开始块内每行都是key: value块与块之间用空行分隔。完整例子如下# Generated by workbuddy-to-dsh 1.2 # source: ~/.workbuddy/export.json task idwb_001 title: 整理季度复盘材料 status: done priority: P1 due: 2026-03-31T18:00:0008:00 owner: zhangsan labels: review,ppt,report desc: 把各类指标导成图表核对上季度数字。选这种文本格式有一个好处它对人类可读对机器也可解析。中间任何一步出问题可以直接打开文件看是哪一行语法错误不必借助复杂调试工具。解析器拿到文件后按空行和task切块再按冒号分隔属性和值逻辑非常简单。文本格式也天然适合 diff转完的文件放进版本库每次改动都能看出是谁、改了什么。2.3 字段映射不是乱写的转换工具的核心是映射关系。由于 DSH 需要的字段名跟 WorkBuddy 不一定一致比如 WorkBuddy 叫assigneeDSH 叫owner所以必须显式声明。我的做法是维护一个 YAML 配置文件每个目标字段写清楚来源字段和处理函数。映射表大致如下源字段DSH 字段处理方式idid直接复制titletitle去掉首尾空白statusstatus枚举映射completed→doneprioritypriority数字 1/2/3 映射为 P1/P2/P3duedue本地时间转 ISO 8601 时区projectproject直接复制labelslabels数组转逗号分隔assigneeowner缺失时填 unassignednotesdesc多行内容转义后输出映射写清楚还有一个附加好处下游 DSH 如果增加新字段不需要改 Python 代码只要在 YAML 里加一行映射工具就能按新字段输出。这也是我坚持把映射放进配置文件而不是写死在代码里的原因。写死在代码里维护成本高改一个字段名就要重新发脚本还会出现不同机器上脚本版本不一样的问题。3. 安装与第一跑通3 个命令完成一次转换3.1 环境准备工具本身用 Python 3 实现我建议至少 3.9 以上因为用了标准库里的时区处理能力太低版本会遇到时区解析问题。唯一额外的第三方依赖是 PyYAML用来读取映射配置。安装命令pip install pyyaml如果你不想装任何第三方库也可以把映射配置改成 JSON 文件但我还是推荐 YAML因为带注释方便在映射表里写清楚每个字段为什么这样处理。配置文件建议和脚本一起放进同一个项目目录并用版本库管理这样之后改映射规则能留下历史记录。3.2 下载脚本和基本用法把workbuddy_to_dsh.py保存到项目目录先不要直接生成文件用 dry-run 检查输出是否符合预期python workbuddy_to_dsh.py export \ --input ~/.workbuddy/export.json \ --mapping mapping.yml \ --output tasks.dsh \ --dry-run--dry-run会在终端打印转换后的 DSH 内容但不写文件。我建议第一次接触这个工具的读者先跑这一步确认字段名和顺序没有明显问题再进入正式转换。确认无误后去掉--dry-run执行python workbuddy_to_dsh.py export \ --input ~/.workbuddy/export.json \ --mapping mapping.yml \ --output tasks.dsh如果之前已经跑过一次想只导出最近改动的任务加上--since参数python workbuddy_to_dsh.py export \ --input ~/.workbuddy/export.json \ --mapping mapping.yml \ --output tasks.dsh \ --since state.txt3.3 参数速查使用过程中最常用的参数我整理成了一张表参数作用默认值--inputWorkBuddy 导出 JSON 路径无必填--mappingYAML 映射配置路径mapping.yml--output输出的 DSH 文件路径无必填--dry-run只打印不写文件关闭--since增量导出的状态文件路径无表示全量--project只导出指定项目不筛选--encoding输出文件的编码utf-8--verbose打印每条任务的处理日志关闭--project这个参数很实用。比如导出文件里混了五六个项目但当前流水线只需要处理其中一个不用改映射配置直接按项目筛掉其他任务。--verbose则用来排查问题打开后每条任务如果出现字段缺失、日期格式无法识别都会打印一行 warning定位速度会快很多。3.4 第一次转换的输出与验证执行正式转换后可以用两行命令快速验证结果是否完整grep -c ^task tasks.dsh grep -c ^[a-z].*: tasks.dsh第一行统计任务块数量应该和 WorkBuddy 里导出的任务总数一致。第二行统计字段行数量如果数量明显偏少说明部分任务字段被过滤掉了需要回去看映射配置。更稳妥的验证方式是把输出文件存一份快照后续每次转换都跟快照做 diffdiff (tail -n 2 old_snapshot.dsh) tasks.dsh因为 DSH 文件开头带生成时间比较时跳过前两行注释。如果 diff 结果里只有预期改动的任务说明转换过程没有引入意外变化。这一步做好之后再让下游看板接入 DSH 文件心里就有底了。4. 核心转换规则是怎么实现的4.1 日期与时区处理日期是最容易翻车的地方。WorkBuddy 里的due经常是2026-03-31 18:00这种本地字符串不带时区。DSH 解析器希望拿到带时区的 ISO 8601 字符串这样不同机器上读到的时间是同一时刻。我的处理函数如下from datetime import datetime, timezone, timedelta DEFAULT_TZ timezone(timedelta(hours8)) def to_iso8601(value, tzDEFAULT_TZ): if value is None or str(value).strip() : return None raw str(value).strip() # WorkBuddy 常见格式YYYY-MM-DD HH:MM if T not in raw and raw.count(:) 1: dt datetime.strptime(raw, %Y-%m-%d %H:%M) return dt.replace(tzinfotz).isoformat() # 已经是 ISO 格式尝试保留 if raw.endswith(Z): dt datetime.fromisoformat(raw.replace(Z, 00:00)) return dt.astimezone(tz).isoformat() dt datetime.fromisoformat(raw) if dt.tzinfo is None: dt dt.replace(tzinfotz) return dt.isoformat()实现要点是“没有时区就补默认时区有 UTC 就转成默认时区”。为什么不能直接用系统本地时区因为同一份导出文件在部署机器和本机转换系统时区可能不同产出结果就会不一样。配置里显式写默认时区能保证任何环境跑出来的时间一致。这对后续定时任务特别重要否则周一早上同步的数据可能出现“时差怎么看都差八小时”的问题。4.2 嵌套对象与数组的序列化WorkBuddy 的数据除了扁平字段还有数组和少量嵌套对象。数组我默认转成逗号分隔例如labels: review,ppt,report嵌套对象则按层级太深的字段用点号展平。展平逻辑很简单def flatten(data, prefix): for key, value in data.items(): full_key f{prefix}.{key} if prefix else key if isinstance(value, dict): yield from flatten(value, full_key) elif isinstance(value, list): yield full_key, ,.join(str(item) for item in value) else: yield full_key, value需要特别小心的是数组元素本身带逗号的情况。比如标签[A, B, C]直接拼接会变成A, B,C下游解析时很难还原。我在工具里会对逗号和反斜杠做转义数组元素里的逗号写成\,解析端再反转义回来。DSH 这种文本格式本身就是约定轻量协议协议里必须明确规定特殊字符怎么处理否则转换工具做得再漂亮也会在下游出错。4.3 空值和缺失字段的兜底策略WorkBuddy 导出 JSON 里不是每条任务都有全部字段。有的没指派负责人有的备注为空有的due可能没填。工具默认行为是字段值为 None、空字符串、空数组时直接跳过这个字段不输出。因为大多数 DSH 解析器读到due:后面没内容会当成格式错误不如不输出让下游使用默认值。如果某个字段来源不存在但映射配置里写了默认值则会输出默认值。比如owner的默认值我设成unassigned这样看板上至少能看到一条任务没有负责人而不是缺失属性。另一个容易踩坑的点是project字段有些旧任务没有项目归属直接跳过会导致看板筛选少统计我的做法是在映射里给 project 也设默认值保证任务永远不会从看板视图里“消失”。4.4 状态枚举和字段改名不同工具的状态词不一致。WorkBuddy 内部可能是completedDSH 约定用donein_progress要转成doing。这些枚举映射写死在代码里不够灵活我放在 YAML 配置的 enum 段里enum: status: completed: done in_progress: doing todo: todo当工具遇到未在枚举表里出现的值比如status: canceled会先输出 warning再原样写到 DSH。这里的原则是“不要静默吞掉不认识的值”否则下游把一条 canceled 任务当 done 处理远比多一条 warning 危险。如果你不希望原样输出可以在配置里加strict_enum: true让工具直接失败强制要求你补充枚举。4.5 增量导出是怎么做到的全量导出逻辑简单但任务多之后每次都生成整份 DSH 文件下游解析时间变长diff 里也全是噪声。工具用--since参数支持增量导出读一个状态文件文件里保存上一次执行的最大updatedAt时间本次只输出updatedAt大于该时间的任务并用这一批生成 DSH。处理完成后把新的最大时间写回状态文件。状态文件内容很简单只有一行last_run2026-03-30T09:12:34Z增量导出要特别说明一点它只能感知新增和修改感知不到删除。如果某条任务从 WorkBuddy 里被删除导出 JSON 里不会再有它工具也不会主动去下游把对应任务块删掉。我的处理办法是每周安排一次全量导出跟存量 DSH 做 diff人工确认哪些任务块需要清理。增量负责日常快全量负责周期纠偏两者配合才是最稳的状态。5. 一个完整实操案例任务看板同步5.1 需求设定某个团队的内部看板消费一种 DSH 文件文件里每个task块对应一块卡片。团队每人会在 WorkBuddy 里维护自己的待办星期一早上看板需要把最新任务同步过去供周会使用。任务量大约一千条手工处理不现实。需求其实很简单每周自动从 WorkBuddy 导出所有未完成任务转成 DSH并保证时间、负责人、标签、优先级都准确。5.2 配置文件和命令我按看板需要的最小字段集写了映射配置没有把 WorkBuddy 的每个字段都塞进去只保留了 id、title、status、priority、due、owner、labels、desc 这八个。因为看板不需要createdAt和updatedAt但增量导出依赖updatedAt所以工具内部会读它却不会把它写进 DSH。命令如下python workbuddy_to_dsh.py export \ --input ~/.workbuddy/export.json \ --mapping configs/board.yml \ --output /data/board/feed/tasks.dsh \ --since /data/board/feed/state.txt \ --project analysis--project analysis让工具只导出 analysis 这个项目其他项目不进看板。如果以后新增项目只需要在计划任务表再加一条命令不影响现有逻辑。5.3 流水线脚本里的调用直接把命令放进计划任务虽然可行但不够健壮。我通常包一层 shell 脚本#!/usr/bin/env bash set -euo pipefail export LANGC.UTF-8 PYTHON/usr/bin/python3 PROJECT_DIR/opt/wb2dsh $PYTHON $PROJECT_DIR/workbuddy_to_dsh.py export \ --input $HOME/.workbuddy/export.json \ --mapping $PROJECT_DIR/configs/board.yml \ --output /data/board/feed/tasks.dsh \ --since /data/board/feed/state.txt \ --project analysis if [ $? -ne 0 ]; then echo workbuddy-to-dsh export failed exit 1 fi这里有两个经验。第一是set -euo pipefail脚本里一旦有命令失败就立刻退出避免生成半截文件骗过下游。第二是在计划任务环境里显式指定 Python 绝对路径因为计划任务的 PATH 很干净直接写python很可能找不到命令。我最初就栽在这上面脚本本地跑没问题进计划任务后一直报 command not found。5.4 实际效果与约束这套方案跑起来后一千多条任务从读取到生成 DSH 文件实测不到两秒大部分时间花在 JSON 解析和 DSH 写入上。每周一自动同步几十条增量基本是秒级完成。这个案例也让我重新理解了“约束”这件事DSH 是文本协议只适合传结构化文本不适合传附件和图片。任务备注里如果贴了截图转换成 DSH 时只会把图片路径写进 desc原始图片还得靠其他方式同步。当初设计映射时就把附件字段排除在外避免让文本协议背不动的包袱。6. 常见问题排查实录6.1 中文乱码和编码问题WorkBuddy 导出 JSON 通常是 UTF-8但很多使用系统默认编码的桌面程序读文本文件时默认按本地编码解码打开的 DSH 文件全变乱码。我的默认编码是utf-8这符合 Linux 和现代工具习惯。如果下游确实需要兼容旧桌面程序可以指定--encoding utf-8-sig输出文件会带 BOM表格类程序就能正确识别。但要注意 BOM 会污染 Unix 端解析所以只有确认下游需要时才用它。排查乱码时先看文件头几个字节再用文件信息命令确认编码别一上来就怀疑代码。6.2 日期格式解析失败WorkBuddy 不同版本导出的日期格式不固定。我见过2026-03-31 18:00、2026/03/31 18:00、2026-03-31三种甚至还有相对时间“今天下午”。相对时间是转换器最难处理的需要业务规则才知道“今天”到底是哪天。我的建议是在工具里只支持明确的绝对时间格式遇到无法解析的值打 warning 并跳过该字段同时提醒用户在 WorkBuddy 里把时间改标准。绝对不要在脚本里用一堆正则猜日期猜错的代价比不输出更大。6.3 输出文件换行符不一致在 Windows 上写文本文件默认会得到\r\n而 DSH 解析器按\n切行最后一行可能出现一个\r残留在字段值里。解决方法很简单open 输出文件时显式指定newline\n。Python 的 open 函数支持这个参数确保无论在哪台机器运行生成的 DSH 都是统一的 LF 换行。如果已经生成了错误换行的文件可以用转换命令修正但最关键的是在代码里一次根治。6.4 字段值里带冒号和反斜杠DSH 用冒号分隔键值如果标题或备注里出现:解析器会误判。我的工具默认对所有输出值做转义反斜杠转成\\冒号转成\:换行转成\n。这样 DSH 文件里每一行仍然只有一个键值分隔冒号。下面的代码是整个工具里最简单但最重要的一段def escape_value(value): value str(value) value value.replace(\\, \\\\) value value.replace(:, \\:) value value.replace(\n, \\n) return value.strip()有人会觉得转义后文本不直观但它保证了协议稳定。下游解析时先按未被转义的冒号切分再反转义还原这个约定要写进 DSH 协议文档两端都要遵守。6.5 任务数太大时内存和性能问题WorkBuddy 单文件通常几千条任务json.load直接读入内存不存在问题。但如果未来扩展到几万条建议改用流式解析。我的脚本目前保留json.load的简单实现因为数据结构是整体数组流式解析需要把文件拆成 JSONL 或换协议成本和收益不成比例。更值得关注的是 DSH 写出性能频繁字符串拼接在大数据量下很慢我在实现里用列表收集行再用\n.join输出实测比一行行写文件快不少。7. 一些值得单独说的经验7.1 测试先行别等接下游才试转换工具最容易陷入“看起来能跑就行”的陷阱。我建议在开发阶段就准备一个包含各种边界情况的 fixture空字段任务、无标签任务、带特殊字符标题、不同日期格式、超长备注然后用一小段测试断言输出 DSH 的每一行。我用的测试框架很轻量本质就是一组断言。这样后续改映射规则时回路非常短几分钟就能确认没有破坏已有规则。没有测试你只能依赖下游系统反馈出问题时定位链路很长。7.2 把转换结果纳入版本控制DSH 是文本格式天然适合放进版本库。每次转换如果产生增量我习惯把结果作为一个文件提交提交说明写“sync from WorkBuddy”。这样做有两个好处一是任何一次 DSH 内容变化都能追溯二是如果下游看板显示错误能快速对比之前和之后的 DSH判断问题是出在数据源还是映射配置。如果把转换结果排除在版本控制之外你会失去很多排查线索。7.3 只映射下游真正需要的字段刚开始写映射配置时我倾向于把 WorkBuddy 所有字段都转进 DSH觉得这样可以“保留所有信息”。实际运行一段时间后发现字段太多让 DSH 文件很难读而且下游根本不消费大部分字段白白占用解析时间。后来我把映射精简到下游确认需要的最小集文件瘦身一半排查问题时一眼能看出来哪几行是有效数据。处理 JSON 原始数据时信息冗余没关系生成给下游消费的 DSH 时按需产出才是合理的。7.4 在自动转换和消费之间留人工检查窗口完全自动化能省时间但不能完全自动消灭错误。我的最终方案是平时用增量导出让流程自动跑每周一全量导出后保留一个检查文件由团队里一个人花五分钟扫一眼确认没有异常的日期和重复的 task id再替换线上文件。别看这五分钟很原始它过滤掉的问题比任何智能检测都多。任务记录是人写的人会在状态枚举上拼错会在日期上填错这类问题是纯格式转换无法纠正的必须留一道人工兜底。我在实际使用中体会最深的一句话是格式转换不是字符串替换而是一套完整的数据契约。上游 JSON 的字段哪天变了下游 DSH 的协议哪天改了工具都应该在第一时间暴露出来而不是悄悄吞掉错误。workbuddy-to-dsh 让我的任务数据在 WorkBuddy 与看板之间流动起来如果你想把自己的某个记录工具也接到某种格式上这套“配置映射 增量导出 测试兜底”的思路可以直接抄过去。先把一条任务转通再慢慢增加字段比一开始就追求覆盖所有情况要稳得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →