{"record":{"id":"9da810ad5bc7ae93","repo":"XTLS/Xray-core","slug":"idkey-not-found-in-json-context","errorCode":null,"errorMessage":"${idKey} not found in JSON context","messagePattern":"(.+?) not found in JSON context","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"infra/conf/loader.go","lineNumber":64,"sourceCode":"\tid = strings.ToLower(id)\n\tconfig, err := v.cache.CreateConfig(id)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := json.Unmarshal(raw, config); err != nil {\n\t\treturn nil, err\n\t}\n\treturn config, nil\n}\n\nfunc (v *JSONConfigLoader) Load(raw []byte) (interface{}, string, error) {\n\tvar obj map[string]json.RawMessage\n\tif err := json.Unmarshal(raw, &obj); err != nil {\n\t\treturn nil, \"\", err\n\t}\n\trawID, found := obj[v.idKey]\n\tif !found {\n\t\treturn nil, \"\", errors.New(v.idKey, \" not found in JSON context\").AtError()\n\t}\n\tvar id string\n\tif err := json.Unmarshal(rawID, &id); err != nil {\n\t\treturn nil, \"\", err\n\t}\n\trawConfig := json.RawMessage(raw)\n\tif len(v.configKey) > 0 {\n\t\tconfigValue, found := obj[v.configKey]\n\t\tif found {\n\t\t\trawConfig = configValue\n\t\t} else {\n\t\t\t// Default to empty json object.\n\t\t\trawConfig = json.RawMessage([]byte(\"{}\"))\n\t\t}\n\t}\n\tconfig, err := v.LoadWithID([]byte(rawConfig), id)\n\tif err != nil {\n\t\treturn nil, id, err","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/infra/conf/loader.go#L46-L82","documentation":"Returned by JSONConfigLoader.Load when the top-level JSON object does not contain the loader's identity key (idKey, e.g. \"protocol\" for inbounds/outbounds). The loader first locates the id key to decide which config creator to invoke; its absence means the JSON fragment cannot be typed. This operates on JSON fragments (like an inbound object), not the whole root config.","triggerScenarios":"Feeding JSONConfigLoader a config fragment missing the id key — e.g. an inbound object without a protocol field, or calling loader.Load with the wrong loader whose idKey differs from the JSON's actual keys.","commonSituations":"Programmatic use of the conf package where callers pass a raw settings object when the loader expects the full inbound/outbound envelope; JSON typos renaming the protocol key (e.g. 'proto').","solutions":["Ensure the JSON object passed to the loader contains the id key (for inbounds/outbounds: \"protocol\")","Pass the complete inbound/outbound object, not just its settings sub-object, to the loader"],"exampleFix":"// before\nLoad([]byte(`{ \"settings\": {} }`))\n// after\nLoad([]byte(`{ \"protocol\": \"freedom\", \"settings\": {} }`))","handlingStrategy":"validation","validationCode":"// Ensure the fragment contains the id key before loading\nvar obj map[string]json.RawMessage\nif err := json.Unmarshal(raw, &obj); err != nil {\n\treturn err\n}\nif _, ok := obj[\"protocol\"]; !ok {\n\treturn errors.New(\"fragment is missing the protocol key\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Pass the complete inbound/outbound envelope (with protocol key) to JSONConfigLoader, not the settings object","Programmatic callers: check obj[idKey] presence before calling Load"],"tags":["xray","config","loader","json","api"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}