JSON存储的手动去重模式 | AI生成和翻译

Home 2026.05

问题: 在添加记录时是否应该强制唯一性——即add_record中的去重逻辑是否是处理JSON存储中”唯一键”问题的正确位置?

回答:

是的,你在add_record中使用的_record_key + existing_keys集合方案正是JSON存储的正确模式。你实际上手动实现了数据库UNIQUE约束的功能。

但有几个需要注意的改进点:

当前逻辑正确但有一个脆弱点

def _record_key(record_type: str, rec: dict) -> str:
    if record_type == "client_timesheet":
        return f"{rec.get('week_start', '')}_{rec.get('week_end', '')}"
    elif record_type == "vendor_punch_card":
        return rec.get("date", "")
    elif record_type == "vendor_timesheet":
        return rec.get("date", "")
    return str(sorted(rec.items()))  # ← 脆弱的回退方案

回退方案str(sorted(rec.items()))会在新增记录类型时静默失效——它会按完整内容相等性进行”去重”,这几乎从来不是你想要的。建议改为显式处理:

def _record_key(record_type: str, rec: dict) -> str:
    if record_type == "client_timesheet":
        return f"{rec['week_start']}_{rec['week_end']}"
    elif record_type in ("vendor_punch_card", "vendor_timesheet"):
        return rec["date"]
    raise ValueError(f"Unknown record_type: {record_type!r}")

失败时抛出异常,而不是静默处理。

真正的唯一性漏洞:跨月份问题

你的去重只检查了同一个月桶内的数据:

existing_keys = {_record_key(record_type, r) for r in all_data[month]["records"]}

2026-05-31这样的打卡日期,如果调用者传入错误的month,理论上可能同时落在202605202606两个桶中。对于打卡记录和供应商工时表,自然键是date本身——建议验证日期确实属于声明的月份:

def _validate_month(record_type: str, rec: dict, month: str) -> bool:
    date_field = rec.get("date") or f"{rec.get('week_start', '')}"
    return date_field.startswith(f"{month[:4]}-{month[4:]}")

add_record的返回值没有在可见的地方被使用

你返回了(added, skipped),但调用方应该记录这个信息:

added, skipped = add_record(data_dir, staff_id, record_type, month, records)
if skipped:
    print(f"  ↩ {skipped} 个重复记录被跳过,{added} 个已添加")

否则静默跳过看起来就像静默失败。

总结

在这个规模下,你的JSON加手动去重方案是正确且足够的。这个模式与SQLite的功能对应关系清晰:

SQLite 你的JSON方案
PRIMARY KEY (date) 返回date_record_key
INSERT OR IGNORE if key in existing_keys: skip
UNIQUE (week_start, week_end) f"{week_start}_{week_end}" 复合键

无需数据库——只需加强回退方案并添加跳过记录的日志即可。


Back Donate