가공 규칙 이해하기
sourceRoot·isArray·filters·fieldMappings가 어떤 순서로 적용되는지와 각각의 함정.
공공 API의 응답은 그대로 쓰기 불편한 경우가 많습니다. 실제 데이터가 네 단계 안쪽에 들어 있거나, 숫자가 문자열로 오거나, 필드 이름이 ctprvnNm 같은 식입니다. 가공 규칙은 이걸 화면에서 바로 쓸 모양으로 바꿉니다.
규칙은 네 부분입니다
{
"sourceRoot": "response.body.items.item",
"isArray": true,
"filters": [{ "field": "sidoName", "op": "eq", "values": ["서울"] }],
"fieldMappings": [
{ "source": "stationName", "output": "station", "transforms": [] },
{ "source": "pm10Value", "output": "pm10", "transforms": [{ "type": "toNumber" }] }
]
}
적용 순서
sourceRoot → isArray → filters → fieldMappings 순으로 처리됩니다. 이 순서가 결과를 좌우하므로 먼저 이해해 두는 편이 좋습니다.
전형적인 공공데이터 응답을 예로 들면 이렇습니다.
{
"response": {
"header": { "resultCode": "00" },
"body": {
"items": { "item": [
{ "stationName": "종로구", "sidoName": "서울", "pm10Value": "45" },
{ "stationName": "해운대구", "sidoName": "부산", "pm10Value": "31" }
] },
"totalCount": 2
}
}
}
sourceRoot—response.body.items.item을 따라가 두 항목이 든 배열만 남깁니다.isArray—true이므로 그 배열을 그대로 항목 목록으로 씁니다.filters—sidoName이"서울"인 항목만 남습니다. 한 개가 됩니다.fieldMappings— 남은 항목을{ station, pm10 }모양으로 다시 만듭니다.
[{ "station": "종로구", "pm10": 45 }]
sourceRoot — 데이터가 있는 곳까지 파고들기
점과 대괄호를 모두 씁니다. a.b.c, a[0].b, a.0.b가 전부 같게 동작합니다.
response.body.items.item 중첩 객체를 따라감
data[0].list 배열의 첫 번째 항목
result.items[*].detail 첫 번째 non-null 항목의 detail
[*]는 모든 항목을 순회하지 않습니다. 배열에서 값이 있는 첫 항목 하나를 고를 뿐입니다. 목록 전체를 다루려면 [*] 없이 배열까지만 지정하고 isArray를 켜세요.
경로가 없으면 결과 전체가 null이 됩니다. 예외가 나지 않으니 조용히 빈 결과가 돌아옵니다. 결과가 계속 null이면 경로부터 의심하세요 — 원본 응답에서 오타나 단수/복수(item vs items)를 확인하면 대개 여기서 걸립니다.
비워 두면 응답 전체를 대상으로 씁니다.
isArray — 목록인가 단건인가
true면 결과가 배열, false면 객체 하나입니다.
true인데 대상이 배열이 아니면 → 길이 1짜리 배열이 됩니다.false인데 대상이 배열이면 → 첫 번째 항목만 남습니다.false인데 필터로 전부 걸러지면 →null.true인데 전부 걸러지면 → 빈 배열[].
filters — 항목 골라내기
여덟 가지 연산자가 있습니다.
| 연산자 | 의미 |
|---|---|
eq / in |
values 안에 있으면 통과 |
ne / nin |
values 안에 없으면 통과 |
gt / gte |
values[0]보다 크다 / 크거나 같다 |
lt / lte |
values[0]보다 작다 / 작거나 같다 |
알아 둘 점이 몇 가지 있습니다.
eq와 in은 동작이 같습니다. 둘 다 values 배열 전체를 후보로 봅니다. eq에 값을 여러 개 넣으면 그중 아무거나 맞으면 통과합니다. ne와 nin도 마찬가지입니다.
필터가 여러 개면 전부 만족해야 합니다(AND). OR 조건은 지원하지 않습니다.
타입 비교가 연산자마다 다릅니다. eq/in은 엄격해서 문자열 "3"과 숫자 3이 다른 값입니다. 반면 gt·lt 계열은 양쪽을 숫자로 바꿔 비교하므로 "25"와 25가 같게 취급됩니다.
{ "field": "pm10Value", "op": "gt", "values": ["30"] }
원본이 문자열 "45"여도 이 필터는 통과합니다.
field에는 중첩 경로를 쓸 수 없습니다. fieldMappings의 source와 달리 필터는 최상위 키만 봅니다. "a.b"라고 쓰면 그런 이름의 키를 찾습니다. 중첩된 값으로 거르려면 먼저 sourceRoot로 내려가세요.
필터가 하나라도 있으면 객체가 아닌 항목은 전부 탈락합니다. 문자열 배열을 다룰 때 필터를 걸면 결과가 통째로 비는 이유입니다.
fieldMappings — 원하는 모양으로 다시 만들기
결과에는 매핑한 필드만 남습니다. 원본 필드가 자동으로 따라오지 않으므로, 필요한 값은 전부 매핑해야 합니다.
source에는 sourceRoot와 같은 점·대괄호 표기를 쓸 수 있습니다. 없는 필드를 지정하면 오류 대신 값이 비게 되고, JSON으로 나갈 때 그 키는 사라집니다. 기본값을 주려면 defaultValue 변환을 붙이세요.
output에도 점 표기를 쓸 수 있어 중첩된 결과를 만들 수 있습니다.
{ "source": "pm10Value", "output": "air.pm10", "transforms": [] }
{ "air": { "pm10": "45" } }
다만 중간 단계는 항상 객체로 만들어집니다. items[0].name 같은 output은 배열이 아니라 { items: { "0": { name: ... } } }가 됩니다.
output이 겹치면 나중 매핑이 이깁니다. 매핑은 최소 1개, 최대 100개까지 넣을 수 있고 필터는 20개까지입니다.
값 자체를 바꾸는 방법은 필드 변환 7종에서 다룹니다.