울면서개발하기
JAVA SCRIPT / NEXACRO

자바스크립트 배열에서 findRowExpr·getCaseCount 처럼 조건으로 행 찾기

· 2026년 10월 25일 다시 씀

투비소프트 XPLATFORM 화면을 인스웨이브 WebSquare5 로 옮기는 프로젝트를 한 적이 있다. XPLATFORM 의 Dataset 에는 조건식으로 행을 찾는 findRowExpr, 조건에 맞는 행 수를 세는 getCaseCount 가 있는데 WebSquare 의 DataList 에는 같은 것이 없었다. “이름이 ○○이 아니고 나이가 28보다 많은 첫 행” 같은 검색을 바로 할 수 없었던 것이다.

그래서 직접 만들어 썼다. WebSquare 가 아니어도 배열을 다루는 자바스크립트라면 어디서든 쓸 수 있다.

예제 데이터:

const people = [
  { 이름: "홍길동", 나이: 25, 지역: "서울" }, // 0
  { 이름: "김철수", 나이: 25, 지역: "서울" }, // 1
  { 이름: "이영희", 나이: 38, 지역: "충북" }, // 2
  { 이름: "박민수", 나이: 50, 지역: "경기" }, // 3
];

방법 1: 조건을 함수로 넘긴다 (권장)

새로 짜는 코드라면 표준 배열 메서드로 충분하다. 조건을 문자열이 아니라 함수로 넘기면 문법 오류를 편집기가 잡아 주고, eval 같은 동적 실행도 필요 없다.

// findRowExpr: 조건을 만족하는 첫 행의 인덱스, 없으면 -1
people.findIndex((row) => row.이름 === "이영희");                  // 2
people.findIndex((row, i) => i !== 0 && row.지역 === "서울");      // 1

// 뒤에서부터 찾기 (ES2023)
people.findLastIndex((row) => row.지역 === "서울");                // 1

// getCaseCount: 조건을 만족하는 행 수
people.filter((row) => row.나이 > 40).length;                       // 1

findIndex 는 조건에 맞는 행을 찾는 즉시 멈춘다. 첫 행만 필요할 때 filter(...)[0] 처럼 전체를 도는 것보다 낫다.

방법 2: “$:컬럼” 조건식 문자열을 그대로 쓴다

기존 화면을 옮기는 중이라 "$:나이 > 40" 같은 조건식 문자열이 이미 수백 군데 있다면, 문자열을 함수로 바꿔 주는 함수를 하나 두는 편이 빠르다.

// "$:컬럼" 을 "row.컬럼" 으로 바꾸고 함수로 만든다.
// 식 안에서 row(현재 행), rowidx(행 번호)를 쓸 수 있다.
function compileExpr(expr) {
  const body = expr.replaceAll("$:", "row.");
  return new Function("row", "rowidx", `return (${body});`);
}

function findRowExpr(rows, expr, start = 0) {
  const test = compileExpr(expr);
  for (let i = start; i < rows.length; i++) {
    if (test(rows[i], i)) return i;
  }
  return -1;
}

function getCaseCount(rows, expr) {
  const test = compileExpr(expr);
  return rows.reduce((count, row, i) => count + (test(row, i) ? 1 : 0), 0);
}

findRowExpr(people, "$:이름 === '이영희'");                          // 2
findRowExpr(people, "$:나이 !== 25");                                 // 2
findRowExpr(people, "$:나이 > 40");                                   // 3
findRowExpr(people, "rowidx !== 0 && $:지역 === '서울'");             // 1
findRowExpr(people, "$:이름.includes('민') && $:지역 !== '충북'");    // 3
findRowExpr(people, "$:지역 === '부산'");                             // -1

getCaseCount(people, "$:나이 !== 25");                                // 2
getCaseCount(people, "rowidx !== 0 && $:지역 === '서울'");            // 1

주의할 점이 있다.

  • new Function 은 eval 처럼 문자열을 코드로 실행한다. 조건식은 개발자가 코드에 쓴 것만 넘기고, 사용자 입력이 섞이지 않게 한다. 보안 점검에서 지적될 수 있으니 이 함수 한곳에만 두는 것이 좋다.
  • "$:" 를 단순 치환하므로 조건식의 문자열 값 안에 $: 가 있으면 함께 바뀐다.
  • 찾지 못하면 -1 을 돌려주게 했다. 넥사크로 Dataset 의 findRowExpr 도 찾지 못하면 -1 을 돌려주고, Array.prototype.findIndex 와도 같아서 다루기 쉽다.

덤: WebSquare5 DataList 필터

같은 조건식으로 WebSquare5 DataList 에 필터를 거는 함수도 만들어 썼다. setColumnFilter 에 type: "func" 로 판정 함수를 넘기면, 함수가 true 를 돌려준 행만 남는다.

function applyFilter(dataList, expr) {
  if (!dataList) return;
  dataList.clearFilter();          // 기존 필터 해제
  if (!expr) return;

  const test = compileExpr(expr);
  dataList.setColumnFilter({
    type: "func",
    colIndex: 0,
    key(cellData, param, rowIndex) {
      return test(dataList.getRowJSON(rowIndex), rowIndex);
    },
    condition: "and",
  });
}

applyFilter(dl_main, "$:국가 !== '대한민국'");

화살표 함수·replaceAll 같은 문법은 프로젝트의 대상 브라우저에 맞춰 바꾼다. 구형 브라우저까지 지원해야 하면 function 표현식과 split("$:").join("row.") 로 쓰면 된다.

다시 쓰며 바로잡은 점

  • 원문 구현은 filter 콜백 안에서 원본 데이터의 각 행에 rowidx 속성을 덧붙였다. 호출할 때마다 원본이 바뀌는 부작용이 있어서, 행 번호는 인자로만 넘기도록 고쳤다.
  • 원문의 findRowExpr 는 첫 행을 찾은 뒤에도 배열 끝까지 돌았고, 못 찾으면 undefined 를 돌려줬다. 찾으면 바로 멈추고 못 찾으면 -1 을 돌려주게 바꿨다.
  • eval 은 호출한 함수의 지역 변수까지 건드릴 수 있어서, 지역 범위를 보지 못하는 new Function 으로 바꿨다. 그래도 동적 코드 실행이라는 점은 같다는 것을 밝혔다. 새 코드라면 조건을 함수로 넘기는 방법 1 을 권한다.