나는 무언가를 데이터 형식으로 정리하는 걸 좋아한다. 길게 늘어진 html을 리액트식으로 하면 .map으로 바꾸는 것을 좋아하는데,(시간이 허락하는 한에서)
지난 프로젝트에서 받은 태스크에 대해서 잊지 않기 위해 기록해 볼까 함.
위 프로젝트는 openAI 요즘 핫한 chatGPT의 api를 사용해 이른바 '환자 정보'를 만들어내는 것으로, 아래의 로직은 모두 파이썬 백엔드에서 완성했지만, 데이터형을 한 번 바꾸고 자바스크립트 내부에서 다시 한 번 코딩하는 것으로 기록하려 한다.
플로우는
의 파이프라인을 구상해야 했다.
현재 적용된 내용을 그대로 적을 수는 없으니, 좀 간단하게 조건을 바꾸어서 보면
이것을 서버에서 받은 escaped string으로 바꾸면 이런 형태가 된다.
따라서 이 부분을 파싱해서 그려주면 되었다.
프론트에서만 사용할 것이기 때문에, 가장 그리기 쉬운 형태로 만들기로 했다.
const resultObj: { [key: string]: { phrases: string[]; hide: boolean } } = {};
대략적으로 구상해 보았을 때 이런 형태면 된다고 생각했다.
export const parseGPTToJSON = (string: string) => {
const gptArr = string.split('\n').filter((e) => e.length); // 1. 먼저 newline을 날림.
const resultObj: GptJsontype = {}; // 예상 완성 형태.
try {
let currentTitle = ''; // 오브젝트의 키값을 담아두기 위한 변수
let numberedEntry = ''; // 제목 하위 번호 소제목을 담기 위한 변수
for (let i = 0; i < gptArr.length; i++) {
if (gptArr[i].endsWith('-')) {
// 만약 -가 포함되어있다면, 대제목으로 간주함.
currentTitle = gptArr[i].trim().replaceAll('-', ''); // 대제목에서 - 과 공백을 제거해서 키값으로 만듬.
resultObj[currentTitle] = { phrases: [], hide: false };
} else {
// - 가 없다면, 제목이 아니라 내용으로 간주
const contentArr = gptArr[i].split('.'); // 하위에서의 string 형식은 n. content로 간주해서 먼저 숫자 번호와 내용을 분리함
contentArr.forEach((e) => {
if (e) {
if (/^[1-9]+$/.test(e)) {
// 번호를 기록하기 위한 if
return (numberedEntry = e);
} else {
// 번호가 아니라면 컨텐츠로 한다.
if (numberedEntry) {
// 주어진 번호가 있다면 해당 번호와 함께 컨텐츠를 넣어주고, numberedEntry를 버림.
resultObj[currentTitle]['phrases'] = [
...resultObj[currentTitle]['phrases'],
numberedEntry + '. ' + e.trim(),
];
return (numberedEntry = '');
} else {
// numberedEntry가 없다면, 그냥 해당 스트링 자체만 넣음.
return (resultObj[currentTitle]['phrases'] = [
...resultObj[currentTitle]['phrases'],
e.trim(),
]);
}
}
}
});
}
}
return resultObj;
} catch {
console.log('err');
}
};
의 형태로 파싱을 해보면
이러한 형태로 만들어지는 것을 확인할 수 있다.
직접 렌더를 해 본다면,
{Object.entries(
parseGPTToJSON(
gptResponseString
)
).map(([key, { phrases }], i) => {
return (
<div key={'heading' + i}>
{key}
{phrases.map((p) => {
return <div key={p}>{p}</div>;
})}
</div>
);
})}
이런식으로 두 가지 정도의 문제점이 보인다.
const contentArr = gptArr[i].split('. '); // 하위에서의 string 형식은 n. content로 간주해서 먼저 숫자 번호와 내용을 분리함
// 이 부분에서 gpt가 형성하는 스트링에서 numbered list는 '1. '의 형태로 나오는 것을 이용해, 스플릿을 .가 아니라 . 으로 해준다.
if (e) {
if (!isNaN(Number(e))) {
// 번호를 기록하기 위한 if
return (numberedEntry = e);
}
// 2. 은 1-9가 아니라 !isNaN으로 숫자인지를 체크해준다.
위의 방법으로 두 가지를 해결할 수 있었다.
최근 이런 문제를 할 일이 거의 없었는데 생각보다 재미있게 해결할 수 있었다.