normalizeStringEncodedJSONFields recursively walks a parsed JSON value, finds string-typed `fields` and `tags` properties, parses them as embedded JSON, and substitutes the native object/array. Additionally drops `implementation_notes` and `decision_log` from the parsed fields object — those entries
(v any)
| 207 | // Strings that don't parse as JSON pass through unchanged — covers |
| 208 | // hand-written field values that happen to share a key name. |
| 209 | func normalizeStringEncodedJSONFields(v any) any { |
| 210 | switch x := v.(type) { |
| 211 | case map[string]any: |
| 212 | for k, val := range x { |
| 213 | if s, ok := val.(string); ok && (k == "fields" || k == "tags") { |
| 214 | if parsed, ok := tryParseEmbeddedJSON(s); ok { |
| 215 | if k == "fields" { |
| 216 | parsed = stripDuplicatedFieldsKeys(parsed) |
| 217 | } |
| 218 | x[k] = parsed |
| 219 | continue |
| 220 | } |
| 221 | } |
| 222 | x[k] = normalizeStringEncodedJSONFields(val) |
| 223 | } |
| 224 | return x |
| 225 | case []any: |
| 226 | for i, val := range x { |
| 227 | x[i] = normalizeStringEncodedJSONFields(val) |
| 228 | } |
| 229 | return x |
| 230 | default: |
| 231 | return v |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // stripDuplicatedFieldsKeys removes implementation_notes and |
| 236 | // decision_log from a parsed fields object. The server hydrates both |