{"record":{"id":"41d194cb633b35b0","repo":"laurent22/joplin","slug":"errorprefix-values-for-key-must-be-pairs-of","errorCode":null,"errorMessage":"${errorPrefix}: values for ${key} must be pairs of strings","messagePattern":"(.+?): values for (.+?) must be pairs of strings","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/app-mobile/services/voiceTyping/whisper.ts","lineNumber":53,"sourceCode":"\t\t};\n\t\tconst processOutputSettings = () => {\n\t\t\tif (!('output' in json)) return;\n\t\t\tif (typeof json.output !== 'object') {\n\t\t\t\tthrow new Error(`${errorPrefix}: Field \"output\" is not an object`);\n\t\t\t}\n\n\t\t\tconst getReplacements = (key: string, value: unknown) => {\n\t\t\t\tif (!Array.isArray(value)) {\n\t\t\t\t\tthrow new Error(`${errorPrefix}: ${key} must be an array`);\n\t\t\t\t}\n\n\t\t\t\tconst results: [string, string][] = [];\n\t\t\t\tfor (const replacement of value) {\n\t\t\t\t\tif (!Array.isArray(replacement)) {\n\t\t\t\t\t\tthrow new Error(`${errorPrefix}: values for ${key} must be arrays`);\n\t\t\t\t\t}\n\t\t\t\t\tif (typeof replacement[0] !== 'string' || typeof replacement[1] !== 'string') {\n\t\t\t\t\t\tthrow new Error(`${errorPrefix}: values for ${key} must be pairs of strings`);\n\t\t\t\t\t}\n\n\t\t\t\t\tresults.push([replacement[0], replacement[1]]);\n\t\t\t\t}\n\t\t\t\treturn results;\n\t\t\t};\n\n\t\t\tif ('stringReplacements' in json.output) {\n\t\t\t\tthis.stringReplacements = getReplacements('stringReplacements', json.output.stringReplacements);\n\t\t\t}\n\n\t\t\tif ('regexReplacements' in json.output) {\n\t\t\t\tthis.regexReplacements = getReplacements('regexReplacements', json.output.regexReplacements).map(([key, value]) => {\n\t\t\t\t\treturn [new RegExp(key, 'g'), value];\n\t\t\t\t});\n\t\t\t}\n\t\t};\n","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/app-mobile/services/voiceTyping/whisper.ts#L35-L71","documentation":"Thrown inside `getReplacements` when an entry is an array but at least one of its two elements is not a string. Both the pattern and the replacement must be strings.","triggerScenarios":"Config like `{ \"stringReplacements\": [[ 1, \"b\" ]] }` or a regex entry where the pattern is not a string (regexes must be supplied as string source then compiled by the config loader).","commonSituations":"Authoring error using a number or object instead of a string; for `regexReplacements`, passing an actual `RegExp` literal serialized incorrectly.","solutions":["Ensure both elements of each pair are strings (regex patterns as string sources).","Re-download the model bundle.","Schema-validate pairs as `[string, string]`."],"exampleFix":"// before\n\"regexReplacements\": [[ /^foo/, \"bar\"]]\n\n// after\n\"regexReplacements\": [[ \"^foo\", \"bar\"]]","handlingStrategy":"type-guard","validationCode":"for (const entry of config.output.stringReplacements ?? []) {\n  if (!Array.isArray(entry) || typeof entry[0] !== 'string' || typeof entry[1] !== 'string') {\n    throw new Error('Whisper config: replacement pair must be [string, string]');\n  }\n}","typeGuard":"const isStringPair = (v: unknown): v is [string, string] =>\n  Array.isArray(v) && v.length >= 2 &&\n  typeof v[0] === 'string' && typeof v[1] === 'string';","tryCatchPattern":"null","preventionTips":["Supply regex patterns as string sources, not RegExp objects.","Schema-validate pairs as [string, string].","Re-download the bundle on validation failure."],"tags":["voice-typing","whisper","config","validation"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}