JSON 파일 읽기
- json 파일은 /assets/front 디렉토리 아래에 위치한다.
- 파일은 한국어 번역 파일인 ko.json 과 en.json으로 구성됨
- json 형식의 번역 파일은 프론트에서 다국어 처리를 위해 사용된다.
func ReadJson(fileName string) ([]byte, error) {
// 현재 프로젝트 루트 디렉토리
pwDir, getPwdErr := os.Getwd()
// 에러 발생 시, 에러 리턴. 에러 발생 파일은 스킵할 수 있게 하기 위하여 리턴함
if getPwdErr != nil {
log.Printf("[READ_JSON] Get Pwd Error: %v", getPwdErr)
return []byte{}, getPwdErr
}
file, readErr := os.ReadFile(pwDir + "/assets/front" + fileName)
if readErr != nil {
log.Printf("[READ_JSON] Read File Error\nFileName: %s, Error: %v", fileName, getPwdErr)
return []byte{}, readErr
}
return file, nil
}
JSON 데이터 파싱
- 파일명에 따라 한국어 / 영어로 구분
- json 파일의 형식은 '{First: {Second: { Third: {"단어": "번역내용"}}}}' 의 형식으로 되어있다.
- Third 에서 depth가 더 들어가는 케이스도 있음
type JsonRowStruct struct {
First string
Second string
Third string
Fourth string
}
func ParseJsonData(jsonData []byte) (map[string]interface{}, error) {
// json 파일 데이터 매핑할 맵 변수 선언 -> 키값 아래에 데이터를 매핑시키는 방식으로 관리
var jsonDataMap map[string]interface{}
// json 데이터를 위에서 선언한 Map에 넣어주기
unMarshalErr := json.Unmarshal(jsonData, &jsonDataMap)
if unMarshalErr != nil {
log.Printf("[PARSE_JSON] unmarshal json error: %v", unMarshalErr)
return map[string]interface{}, unMarshalErr
}
return jsonDataMap, nil
}
파싱된 JSON 데이터 평탄화
- 이러한 Depth 들을 평탄화 시켜줘야 액셀 파일에 넣기 용이함
- 평탄화 하지 않으면 depth 별로 for 문을 돌려야 하고, depth 길이에 따른 동적 처리가 어려워 졌었음
// 액셀 파일 데이터 구조체
type FileWork struct {
file *excelize.File
FileName string
SheetLength int
}
// 이미 한글/영어 데이터가 입력 되었을 때, 코드 값을 기준으로 데이터를 입력할 위치 탐색
func (excelData *FileWork)FindCode(sheetName string, rowIndex int, majorDepth , secondDepth, thirdDepth, fourthDepth string) bool {
// 각 깊이별로 셀 값을 가져옴
cellFirst, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("A%d", rowIndex))
cellSecond, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("B%d", rowIndex))
cellThird, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("C%d", rowIndex))
cellFourth, _ := excelData.file.GetCellValue(sheetName, fmt.Sprintf("D%d", rowIndex))
// 데이터 코드 비교
if cellFirst == majorDepth
&& cellSecond == secondDepth
&& cellThird == thirdDepth
&& cellFourth == fourthDepth {
return true
}
return false
}
// 데이터 평탄화
func (excelData *FileWork)FlattenJsonData(sheetName string, parsedData map[string]interface{}) {
switch v := data.(type) {
case map[string]interface{}:
for key, value := range v {
newPrefix := prefix
if prefix != "" {
newPrefix += "."
}
newPrefix += key
// map value의 데이터 역시 depth가 있을 수 있기 때문에 평탄화 시킨다
excelData.flattenKorJSON(sheetName, value)
}
// 문자로 들어왔으면 카테고리 구분하여 처리 - 이미 front 시트에 데이터가 들어가 있을 때
case string:
depths := strings.Split(prefix, ".")
firstDepth := ""
secondDepth := ""
thirdDepth := ""
fourthDepth := ""
if len(depths) > 0 {
firstDepth = depths[0]
}
if len(depths) > 1 {
secondDepth = depths[1]
}
if len(depths) > 2 {
thirdDepth = depths[2]
}
if len(depths) > 3 {
fourthDepth = strings.Join(depths[3:], ".")
}
// 엑셀의 모든 행을 탐색하여 코드 일치 여부 확인
rows, err := excelData.file.GetRows(sheetName)
if err != nil {
log.Printf("GetRows Error: %v", err)
return
}
for rowIndex := range rows {
// 첫 번째 행은 헤더이므로 건너뜀
if rowIndex == 0 {
continue
}
// 1번째 row는 칼럼 값들이므로, 2번째 줄 부터 데이터 입력(index+1)
isCodeMatch := excelData.FindCode(sheetName, rowIndex+1, firstDepth, secondDepth, thirdDepth, fourthDepth)
if isCodeMatch {
// 'F' 열에 값 삽입
excelData.file.SetCellValue(sheetName, fmt.Sprintf("E%d", rowIndex+1), v)
break // 매칭되는 첫 번째 행에만 값을 삽입하고 루프 종료
}
}
default:
// 다른 타입은 처리하지 않음
}
}
액셀 파일 생성 및 데이터 저장
- 데이터를 처리, 액셀 파일 저장하기 위해 github.com/xuri/excelize/v2 외부 패키지를 사용하였다.
- 매핑된 데이터를 평탄화하여 저장한다.
# 패키지 설치
go get -u github.com/xuri/excelize/v2
// 번역 엑셀 파일 존재 여부 체크
func (excelData *FileWork)CheckIfFileExist() bool {
_, err := os.Stat(excelData.fileName)
return !os.IsNotExist(err)
}
// 파일 저장
func (excelData *FileWork) SaveExcelFile() {
savingErr := excelData.file.SaveAs(excelData.FileName)
if savingErr != nil {
log.Printf("[EXCEL_SAVE] Saving File Error: %v", savingErr)
} else {
log.Printf("[EXCEL_SAVE] Saving Success: %s", excelData.file.FileName)
}
}
// 칼럼 설정
func (excelData *FileWork) SetJsonColumns() {
excelData.file.SetCellValue(sheetName, "A1", "First")
excelData.file.SetCellValue(sheetName, "B1", "Second")
excelData.file.SetCellValue(sheetName, "C1", "Third")
excelData.file.SetCellValue(sheetName, "D1", "Ko")
excelData.file.SetCellValue(sheetName, "F1", "En")
}
func (excelData *FileWork)WriteJsonExcelFile(parsedData map[string]interface[}) error {
// 번역 파일 존재 여부에 따라 액셀 파일 여는 방식이 다르므로 미리 선언
// 프로그램 실행 시 먼저 파일을 만들고, 항상 openFile 하는 방법으로 작성하였으나 여기서는 두가지 방식을 보여주기 위해 분리해서 보여줌
if CheckIfFileExist(excelData.fileName) {
file, openErr := excelize.OpenFile(excelFilePath)
if openErr != nil {
log.Printf("[EXCEL_WRITE] Open Excel File for JSON Error: %v", openErr)
// 파일 오픈 실패 시, 새로 파일 생성
excelData.file = excelize.NewFile()
}
excelData.file = file
} else {
excelData.file = excelize.NewFile()
}
sheetIndex, indexErr := excelData.file.GetSheetIndex("front")
if indexErr != nil {
log.Printf("[EXCEL_WRITE] Failed to get Sheet Index for JSON error: %v", indexErr)
return indexErr
}
excelData.SetJsonColumns()
return nil
}