{"record":{"id":"283aff289300f8ac","repo":"JuliusBrussee/caveman","slug":"json-splice-replacement-is-not-valid-json","errorCode":null,"errorMessage":"json splice: replacement is not valid JSON","messagePattern":"json splice: replacement is not valid JSON","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/providers/jsonsplice/jsonsplice.go","lineNumber":142,"sourceCode":"\t\tout = append(out, body[last:candidate.Start]...)\n\t\tout = append(out, quoted...)\n\t\tlast = candidate.End\n\t}\n\tif out == nil {\n\t\treturn body, nil\n\t}\n\treturn append(out, body[last:]...), nil\n}\n\n// ReplaceRaw replaces one JSON value while preserving every byte outside span.\n// replacement must itself be valid JSON. Unlike Replace, it does not quote the\n// replacement and is suitable for provider-envelope edits.\nfunc ReplaceRaw(body []byte, span Span, replacement []byte) ([]byte, error) {\n\tif span.Start < 0 || span.End > len(body) || span.Start >= span.End {\n\t\treturn nil, fmt.Errorf(\"json splice: invalid range\")\n\t}\n\tif !json.Valid(replacement) {\n\t\treturn nil, fmt.Errorf(\"json splice: replacement is not valid JSON\")\n\t}\n\tout := make([]byte, 0, len(body)-(span.End-span.Start)+len(replacement))\n\tout = append(out, body[:span.Start]...)\n\tout = append(out, replacement...)\n\tout = append(out, body[span.End:]...)\n\treturn out, nil\n}\n\n// AppendObjectFields inserts fields immediately before an object's closing\n// brace. Existing bytes, whitespace, key order, and escapes remain untouched.\n// Callers must reject existing decoded keys before calling this function.\nfunc AppendObjectFields(body []byte, object Span, fields ...FieldInsertion) ([]byte, error) {\n\tif object.Start < 0 || object.End > len(body) || object.Start >= object.End ||\n\t\tbody[object.Start] != '{' || body[object.End-1] != '}' {\n\t\treturn nil, fmt.Errorf(\"json splice: invalid object range\")\n\t}\n\tif len(fields) == 0 {\n\t\treturn body, nil","sourceCodeStart":124,"sourceCodeEnd":160,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/proxy/providers/jsonsplice/jsonsplice.go#L124-L160","documentation":"ReplaceRaw requires the replacement bytes to already be valid JSON since they are inserted verbatim into the document. If json.Valid(replacement) is false the splice is aborted to avoid corrupting the envelope.","triggerScenarios":"Passing Go values, plain strings, or partially-serialized buffers as replacement instead of marshaled JSON; hand-built JSON with a syntax error; forgetting json.Marshal on a string value (must be quoted).","commonSituations":"Injecting a cache_control object where the struct was serialized with a non-JSON encoder; interpolating Go strings directly into the replacement; truncated JSON from a failed marshal earlier in the pipeline.","solutions":["Run replacement through json.Marshal (or json.Valid in a unit test) before calling ReplaceRaw","Quote raw strings with json.Marshal(string) so they become valid JSON literals","Check the error of any earlier marshal step instead of ignoring it and passing empty/partial bytes"],"exampleFix":"// before: plain string spliced in\nrepl := []byte(`cache_control`)\nout, err := jsonsplice.ReplaceRaw(body, span, repl)\n// after: marshal to valid JSON\nraw, err := json.Marshal(map[string]any{\"cache_control\": map[string]string{\"type\": \"ephemeral\"}})\nif err != nil { return nil, err }\nout, err := jsonsplice.ReplaceRaw(body, span, raw)","handlingStrategy":"validation","validationCode":"if !json.Valid(repl) { return fmt.Errorf(\"replacement is not valid JSON: %q\", repl) }","typeGuard":null,"tryCatchPattern":"raw, err := json.Marshal(value)\nif err != nil { return nil, err }\nif !json.Valid(raw) { return nil, fmt.Errorf(\"marshal produced invalid JSON\") }\nout, err := jsonsplice.ReplaceRaw(body, span, raw)","preventionTips":["Always produce replacements via json.Marshal, never string concatenation","Quote bare strings with json.Marshal(string)","Check every upstream marshal error; never pass partial bytes through"],"tags":["json","go","byte-splice","validation"],"backgroundTag":"json-parse-error","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}