{"record":{"id":"e2ef1d0854187876","repo":"actualbudget/actual","slug":"failed-to-parse-mapping-string-message","errorCode":null,"errorMessage":"Failed to parse mapping: ${String(message)}","messagePattern":"Failed to parse mapping: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/util/custom-sync-mapping.ts","lineNumber":27,"sourceCode":"      ]),\n    ),\n  );\n\nexport const mappingsFromString = (str: string): Mappings => {\n  try {\n    const parsed = JSON.parse(str);\n    if (typeof parsed !== 'object' || parsed === null) {\n      throw new Error('Invalid mapping format');\n    }\n    return new Map(\n      Object.entries(parsed).map(([key, value]) => [\n        key,\n        new Map(Object.entries(value as object)),\n      ]),\n    );\n  } catch (e) {\n    const message = e instanceof Error ? e.message : e;\n    throw new Error(`Failed to parse mapping: ${String(message)}`);\n  }\n};\n\nexport const defaultMappings: Mappings = new Map([\n  [\n    'payment',\n    new Map([\n      ['date', 'date'],\n      ['payee', 'payeeName'],\n      ['notes', 'notes'],\n    ]),\n  ],\n  [\n    'deposit',\n    new Map([\n      ['date', 'date'],\n      ['payee', 'payeeName'],\n      ['notes', 'notes'],","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/util/custom-sync-mapping.ts#L9-L45","documentation":"mappingsFromString parses a serialized custom-sync mapping string back into nested Maps. Any exception thrown during parsing (malformed JSON, wrong shape, non-object values) is caught and rethrown wrapped in this error with the original message appended, so the root cause is preserved in the message text.","triggerScenarios":"Passing a hand-edited or corrupted mapping string (invalid JSON, top-level value that is not an object of objects), an environment variable or config file containing truncated mappings, or a mappings string produced by a different/older schema version.","commonSituations":"Users hand-editing custom sync mapping config; upgrades changing the mapping schema so old serialized mappings no longer match; shell escaping mangling quotes in an env-var-provided mapping string.","solutions":["Read the wrapped message — the original parse error after 'Failed to parse mapping:' pinpoints the syntax or shape problem.","Validate the mapping string is valid JSON with shape { [category]: { [from]: to } } before calling.","Regenerate the mapping string from the source app instead of hand-editing it.","Fall back to defaultMappings if the custom string cannot be parsed."],"exampleFix":"// before\nconst mappings = mappingsFromString(process.env.CUSTOM_SYNC_MAPPING);\n// after\nlet mappings;\ntry {\n  mappings = mappingsFromString(process.env.CUSTOM_SYNC_MAPPING);\n} catch (e) {\n  console.warn('Invalid mapping, using defaults:', e.message);\n  mappings = defaultMappings;\n}","handlingStrategy":"try-catch","validationCode":"let parsed: unknown;\ntry { parsed = JSON.parse(mappingString); } catch { throw new Error('Mapping is not valid JSON'); }\nconst shapeOk = parsed && typeof parsed === 'object' &&\n  Object.values(parsed).every(v => v && typeof v === 'object' && !Array.isArray(v));\nif (!shapeOk) throw new Error('Mapping must be { category: { from: to } }');","typeGuard":"function isMappingString(s: unknown): s is string {\n  if (typeof s !== 'string') return false;\n  try {\n    const v = JSON.parse(s);\n    return !!v && typeof v === 'object' &&\n      Object.values(v).every(x => x && typeof x === 'object' && !Array.isArray(x));\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  mappings = mappingsFromString(raw);\n} catch (e) {\n  if (e.message.startsWith('Failed to parse mapping:')) {\n    console.warn(`${e.message} — falling back to defaultMappings`);\n    mappings = defaultMappings;\n  } else { throw e; }\n}","preventionTips":["Validate the mapping string's JSON shape before parsing","Generate mappings programmatically instead of hand-editing them","Keep a fallback to defaultMappings for resilience during config mistakes"],"tags":["parsing","configuration","sync","json"],"backgroundTag":"config-parse-failed","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}