빌드노트
Node에서 JSON import가 type 속성을 요구할 때
JSON 경로와 ESM 설정이 맞아도 import의 type 속성이 없으면 실패한다. Node v22.23.2에서 속성 없음·정확한 json·잘못된 타입의 종료 코드와 실제 값을 비교하고, with 구문과 default export 계약을 확인한다.
Node.js에서 import settings from './settings.json'이 ERR_IMPORT_ATTRIBUTE_MISSING으로 실패한다면, JSON 파일 경로를 찾고도 필요한 import 속성이 빠진 경우다. JSON을 ESM으로 가져올 때는 with { type: 'json' }를 명시해야 한다. 자신의 JSON import 문을 import settings from './settings.json' with { type: 'json' };로 고친다. 파일 이름과 변수 이름은 실제 코드에 맞춘다. 이 요구는 Node.js의 import attributes 문서 (새 탭에서 열림)에 명시돼 있다.
package.json의 type과 import의 type은 역할이 다르다
먼저 오류가 난 실행 환경의 Node 버전을 확인한다. 아래 명령은 PowerShell에서 버전과 실제 실행 파일 경로를 함께 보여 준다. IDE나 CI에서 실패했다면 그곳에서 쓰는 Node도 확인해야 한다.
node -p "JSON.stringify({node:process.version,executable:process.execPath})"
이번 별도 실험에서는 node가 v22.23.2, 실행 파일이 C:\nvm4w\nodejs\node.exe였다. 자신의 출력 경로가 다를 수 있다. 버전 이름만 같다고 같은 실행 환경까지 확인한 것은 아니다.
다음으로 JSON을 가져오는 JavaScript 파일의 모듈 형식을 본다. .js라면 그 파일이 있는 폴더부터 위로 올라가며 처음 만나는 package.json의 "type": "module"을 확인한다. .mjs는 ESM으로 해석된다. 이 판단은 Node.js의 모듈 형식 결정 규칙 (새 탭에서 열림)에 따른다. 기존 프로젝트 전체의 type을 무작정 바꾸기보다 오류가 난 파일의 조건부터 읽는다.
package.json의 "type": "module"은 해당 범위의 .js를 ESM으로 읽도록 정한다. import 문 뒤의 type: 'json'은 가져올 대상이 JSON임을 선언한다. 앞의 설정이 있어도 뒤의 속성이 자동으로 붙지는 않는다. .json 확장자를 적은 것만으로도 이 요구를 충족하지 않는다. 아래 비교는 ESM 설정과 파일 경로를 고정한 채 import 속성만 바꾼 실험이다.
같은 JSON에서 속성만 바꿔 실행했다
Node.js가 설치된 환경에서 직접 비교하려면 새 빈 폴더를 사용한다. package.json에는 {"type":"module"}을 넣고, 같은 폴더의 settings.json에는 아래 합성 데이터를 저장한다. 별도 패키지 설치는 필요 없다.
{"name":"json-lab","retries":3,"enabled":false}
정상 조건은 with-json.js에 아래 두 줄을 저장해 확인한다. 첫 줄은 JSON을 가져오고, 둘째 줄은 받은 값을 출력한다.
import settings from './settings.json' with { type: 'json' };
console.log(JSON.stringify(settings));
비교할 파일 두 개는 이 파일을 복사해서 만들고 첫 줄만 다음처럼 바꾼다. 둘째 줄과 settings.json은 그대로 둔다.
no-attribute.js:import settings from './settings.json';wrong-type.js:import settings from './settings.json' with { type: 'javascript' };
세 파일을 저장한 폴더의 PowerShell에서 다음을 실행한다. 각 exit는 바로 앞 Node 명령의 종료 코드다. 여기서는 실패 조건도 비교하므로 앞 명령이 종료 코드 1이어도 다음 조건을 실행한다.
node ./no-attribute.js
"exit=$LASTEXITCODE"
node ./with-json.js
"exit=$LASTEXITCODE"
node ./wrong-type.js
"exit=$LASTEXITCODE"
Windows, PowerShell 7.6.6, Node.js v22.23.2에서 얻은 결과는 다음과 같았다. 실제 서비스의 장애 로그가 아닌, 위 파일들로 따로 실행한 결과다.
- 속성 없음:
TypeError [ERR_IMPORT_ATTRIBUTE_MISSING],exit=1. 오류에는needs an import attribute of "type: json"이 포함됐고 JSON 값은 출력되지 않았다. type: 'json':{"name":"json-lab","retries":3,"enabled":false}출력,exit=0. 표준 오류 출력은 비어 있었다.type: 'javascript':TypeError [ERR_IMPORT_ATTRIBUTE_UNSUPPORTED],exit=1. 오류는Import attribute "type" with value "javascript" is not supported라고 알렸고 JSON 값은 출력되지 않았다.
같은 파일과 같은 ESM 설정에서 정확한 속성을 준 경우만 값을 받았다. 따라서 이 비교에서의 실패 원인은 JSON 데이터나 ESM 모드 변경으로 설명할 필요 없이 import 속성으로 좁힐 수 있다. 속성 이름만 적는 것으로 충분하지 않고 값도 'json'이어야 한다. 실행 버전의 JSON import 규칙 (새 탭에서 열림)과도 일치했다.
정상 코드의 settings는 JSON 모듈의 default export를 받은 변수다. 출력된 객체에서 숫자 3과 불리언 false도 확인했다. 공식 문서상 JSON 모듈은 default export만 제공하므로 JSON의 키를 import { name }처럼 직접 가져오지 않는다. 기본 import로 객체를 받은 뒤 settings.name으로 접근한다. named import의 실패는 이번 세 조건에 포함해 실행하지 않았으며, 이 설명은 JSON 모듈의 export 계약 (새 탭에서 열림)을 따른 것이다.
수정했는데도 결과가 다르면
with 위치에서 구문 오류가 난다면 위의 속성 누락 오류와 구분한다. 실제 실행 Node가 이 문법을 지원하는지, 오류를 낸 주체가 Node인지 앞단의 변환 도구인지부터 확인한다. 예전 예제의 assert { type: 'json' }로 무조건 바꾸지는 않는다. 공식 변경 기록에는 Node v22.0.0에서 import assertions 지원을 제거했고, v22.12.0에서 import attributes와 JSON 모듈이 실험 단계를 벗어났다고 나온다. 이는 문서의 변경 기록 (새 탭에서 열림)을 확인한 사실이며, 여기서 그 두 버전을 각각 실행한 것은 아니다. 다른 버전을 사용한다면 해당 버전 문서의 조건을 확인한다.
ERR_MODULE_NOT_FOUND라면 오류에 나온 경로와 실제 파일을 먼저 대조한다. JSON 구문 오류가 새로 나온다면 JSON 내용도 확인한다. 속성 한 줄을 수정했다는 이유만으로 서로 다른 오류까지 해결됐다고 판단할 수는 없다. 정상 종료했는데 값이 다르면 가져온 파일과 default export에서 읽는 속성을 확인한다.
이번 검증은 사용자 정의 로더와 실험 플래그 없이 설치된 Node v22.23.2가 로컬 JSON을 정적으로 import한 세 조건까지다. 다른 Node 버전, 구 assert 문법의 실제 실행, 동적 import(), 브라우저·TypeScript·번들러의 처리는 실행하지 않았다. 위 최소 예제는 통과하고 원래 실행만 실패한다면, 다음 확인 대상은 원래 실행이 사용하는 Node 경로와 변환 도구의 JSON import 지원 조건이다.