Write down the JSON library policy and fix the drift it already caused
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 52/100
Research direction
Read the JSON guidance in internal/server/json_serializer.go and internal/server/messages_native.go, then inspect the helper pairs in internal/users/store_sql.go, internal/virtualmodels/store.go, and internal/storage/sqlutil/sqlutil.go. Update the guidance and rationale, and fold the store-local helpers into the shared SQL utilities as appropriate. Done when the policy is documented, the inaccurate comment is corrected, and tests for the affected stores and utilities pass.
Written by the indexing model from the issue text.
Description
Noticed while working on #881. Not a bug — no user-visible impact — but the JSON story has three libraries, no written rule, and drift has already started.
What we use today
| Library | Prod files | Job |
|---|---|---|
github.com/goccy/go-json |
146 | Default marshal/unmarshal, including echo's response serializer (internal/server/json_serializer.go) |
encoding/json |
20 | Sites that want exact stdlib semantics or the token-streaming decoder |
github.com/tidwall/gjson |
8 | Read-only path queries over raw bytes, no unmarshal |
gopkg.in/yaml.v3 |
2 | config.yaml |
All four earn their place — they do different jobs, and none is redundant:
- goccy is load-bearing and guarded by the pre-commit hot-path alloc check.
- gjson answers path queries on raw bytes without unmarshalling (selector peek, session detection, cache keys, upstream error-body extraction). goccy has no equivalent API.
- stdlib cannot be dropped anyway: echo's binder uses
DefaultJSONSerializer.Deserialize, andconfig/merge.gorelies onjson.NewDecoderto reject trailing data.
The problem is not the count. It is that the rule for choosing between them is written nowhere — it lives in scattered comments (internal/server/json_serializer.go, internal/server/messages_native.go) — so sites pick a library by whatever the neighbouring file happened to do.
The drift so far
1. Three copies of one helper, split across two JSON libraries.
internal/users/store_sql.go:102/:113—encodeAllowedModels/decodeAllowedModels, usingencoding/jsoninternal/virtualmodels/store.go:35/:60—encodeUserPaths/decodeUserPaths, character-for-character the same idiom, using goccyinternal/storage/sqlutil/sqlutil.go:104/:119—NullableJSONStrings/StringsFromJSON, a third copy (goccy, nullable variant)
The first two back the same column shape (NOT NULL DEFAULT '[]'), so they are interchangeable. sqlutil is already the home for this; the store-local copies should fold into it.
2. An inaccurate rationale. internal/server/messages_native.go:5 says stdlib is used because rewriteMessagesModel "needs the decoder's InputOffset". goccy exposes both Token() and InputOffset() (decode.go:244, :255), so the stated reason does not hold. Probably not worth switching — goccy's InputOffset delegates to TotalOffset(), whose streaming semantics may differ, and there is nothing to gain — but the comment should state the real reason.
3. Deliberate mixing with no explanation. internal/auditlog/body.go imports both, goccy as json and stdlib aliased encjson purely for Valid(). Presumably intentional; nothing says why validation specifically wants stdlib.
Suggested work
- Write the rule down once — a short ADR, or a doc comment somewhere central: goccy for marshal/unmarshal, gjson for read-only extraction from raw bytes, stdlib only where its exact semantics are required, and each such site says which semantics and why.
- Fold
users/store_sql.go's encode/decode pair onto the sharedsqlutilhelpers (and consider thevirtualmodels/store.gopair too). - Correct the
messages_native.gocomment.
No dependency should be removed; the // indirect JSON/YAML entries (google/jsonschema-go, go-openapi/*, sigs.k8s.io/yaml, go.yaml.in/yaml) are all transitive from the MCP SDK, swaggo (behind //go:build swagger) and testify.
- Dominant language
- Go
- Stars
- 1.2k
- Forks
- 111
- Avg merge
- 14h 43m
- Merged PRs (30d)
- 142
Getting set up
- Ships a Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from ENTERPILOT/GoModel
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 55/100
ENTERPILOT/GoModel#1182 ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 15/100
ENTERPILOT/GoModel#1170 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 15/100
ENTERPILOT/GoModel#1165 ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 Half a day Newbie friendliness 35/100
ENTERPILOT/GoModel#1154 ·
Maintainers usually reply within 1 day
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
ENTERPILOT/GoModel#1107 ·
Maintainers usually reply within 1 day
All issues in ENTERPILOT/GoModel
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
Maintainers usually reply within 1 day
-
copilot-api domain:platform go
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
docs: clarify gh api -f vs -F and @-file expansion in the gh skillPossibly taken @MatiasOlivera claimed this today. Openenhancement
Difficulty 2/5 Half a day Newbie friendliness 62/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
aws/aws-advanced-go-wrapper#612 ·
-
Say in the changelog that the text-index update cannot be rolled backPossibly taken @praetorianer777 claimed this today. Open
Difficulty 1/5 Under an hour Newbie friendliness 88/100
praetorianer777/GoTome#275 ·
Maintainers usually reply within 1 day