251209 CSV 파싱

2025. 12. 9. 13:12·📖TIL

데이터를 정리한 엑셀파일을 CSV 로 저장 - Resource 파일에 저장 - CSV를 읽어 파싱 - 리스트에 저장

using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 특정한 테이블의 ROW 객체를 ID를 통해 저장 및 조회할 수 있는 클래스
/// </summary>
/// <typeparam name="TKey">테이블의 ID값</typeparam>
/// <typeparam name="TRow">실제 ROW타입 데이터</typeparam>

public class Table<TKey, TRow> where TRow : TableBase
{
    private readonly Dictionary<TKey, TRow> _data;
    private readonly string _tableName;
    private readonly string _idColumName;

    public IReadOnlyDictionary<TKey, TRow> Data => _data;

    public Table(string tableName, string idColumnName, Dictionary<TKey, TRow> data)
    {
        _tableName = tableName;
        _idColumName = idColumnName;
        _data = data;
    }

    public TRow this[TKey id] // 인덱싱
    {
        get
        {
            if(_data.TryGetValue(id, out TRow row))
            {
                return row;
            }

            Debug.LogError($"존재하지 않는 ID : {id}");
            return null;
        }
    }
}
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Reflection;

/// <summary>
/// CSV 파일을 파싱하고 정의된 ROW클래스에 데이터를 채워넣는 클래스
/// </summary>

public class TableParser
{
    // 실제 컬럼 이름이 적혀있는 행의 인덱스
    private const int HeaderRowIndex = 1;

    // 데이터가 시작되는 첫 줄의 인덱스
    private const int DataStartRowIndex = 3;

    public static Table<TKey, TRow> Parse<TKey, TRow>(
        TextAsset csvFile,
        string idColumName
        ) where TRow : TableBase, new()
    {
        // 입력 검사 : csv 파일이 null 이면 Log 남기고 null 반환
        if (csvFile == null)
        {
            Debug.LogError("CSV 파일이 null 입니다.");
            return null;
        }

        // 1. CSV 내용 읽기 및 줄/셀 분리
        List<string[]> rows;
        try
        {
            // 줄 분리, 분리된 줄 가져와서 ',' 단위 분리
            rows = csvFile.text.
                Split(new[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries)
                .Select(line => line.Split(',').Select(cell => cell.Trim()).ToArray())
                .ToList();
        }
        catch (Exception e)
        {
            Debug.LogError($"CSV 파일 읽기 오류");
            Debug.LogError(e.Message);
            return null;
        }

        // 2. 컬럼 헤더 추출
        if (rows.Count <= HeaderRowIndex)
        {
            // 헤더 행 인덱스가 전체 줄 개수보다 크거나 같다면 헤더가 없다는 의미
            Debug.LogError($"CSV에 컬럼 헤더 {HeaderRowIndex}가 없습니다.");
            return null;
        }

        // 헤더 행을 컬럼명 배열로 취득
        string[] columnNames = rows[HeaderRowIndex];

        // 사용자가 지정한 ID 컬럼 이름이 헤더에 있는지 확인
        int idIndex = Array.IndexOf(columnNames, idColumName);
        if (idIndex < 0)
        {
            Debug.LogError($"CSV에 id 컬럼 헤더 {HeaderRowIndex}가 없습니다.");
            return null;
        }

        // 3. TRow 클래스 속성 및 필드 컬럼명 맵핑
        Type rowType = typeof(TRow);
        Dictionary<string, MemberInfo> memberMap = new Dictionary<string, MemberInfo>();
        foreach (var name in columnNames)
        {
            var prop = rowType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
            if (prop != null)
            {
                memberMap.Add(name, prop);
                continue;
            }

            var field = rowType.GetField(name, BindingFlags.Public | BindingFlags.Instance);
            if (field != null)
            {
                memberMap.Add(name, field);
                continue;
            }
        }

        // 4. 데이터 파싱 및 TRow 인스턴스 생성

        // 최정적으로 결과가 저장될 장소
        Dictionary<TKey, TRow> allRowData = new Dictionary<TKey, TRow>();

        // 데이터 시작 인덱스부터 끝까지 한줄씩 처리 진행
        for (int i = DataStartRowIndex; i < rows.Count; i++)
        {
            // 현재 처리하고자 하는 데이터 행의 셀 배열
            string[] rowValues = rows[i];

            // 행의 셀 갯수가 헤더의 컬럼 갯수보다 작을 경우
            if (rowValues.Length < columnNames.Length)
            {
                if (rowValues.All(string.IsNullOrEmpty))
                {
                    // 빈 것 건너뛰기
                    continue;
                }

                // ID 추출 및 TKey 타입으로 변환

                // id컬럼 문자열 값
                string idString = rowValues[idIndex];

                // id가 비었다는 것은 유효한 데이터가 아니라는 의미로 해석
                if (string.IsNullOrEmpty(idString))
                {
                    continue;
                }

                TKey idValue = default;
                try
                {
                    idValue = (TKey)Convert.ChangeType(idString, typeof(TKey));
                }
                catch (Exception e)
                {
                    Debug.LogError($"id {idString} 변환 실패");
                    Debug.LogError(e.Message);
                }

                if (allRowData.ContainsKey(idValue))
                {
                    Debug.LogError($"{idValue} 중복");
                    {
                        continue;
                    }
                }

                // id 처리 완료

                // 새로운 TRow 인스턴스 생성
                TRow newRow = new TRow();

                // 인스턴스에 값 채우기
                for (int j = 0; j < columnNames.Length; j++)
                {
                    // 컬럼 이름
                    string columnName = columnNames[j];

                    // 읽어온 문자열 값
                    string stringValue = rowValues[j];

                    // TRow 에 멤버가 존재하는지 체크해서 진행
                    if (memberMap.TryGetValue(columnName, out MemberInfo member))
                    {
                        Type targetType = (member is PropertyInfo prop) ? prop.PropertyType : ((FieldInfo)member).FieldType;

                        try
                        {
                            // 현재는 문자열 값으로 되어있으니까 테이블에서 정의한 타입으로 변환해서 매칭 시켜주기
                            object convertedValue = ConvertValue(stringValue, targetType);

                            if (member is PropertyInfo propInfo)
                            {
                                propInfo.SetValue(newRow, convertedValue);
                            }
                            else if (member is FieldInfo fieldInfo)
                            {
                                fieldInfo.SetValue(newRow, convertedValue);
                            }
                        }
                        catch (Exception e)
                        {
                            Debug.LogError($"값 변환 오류: 칼럼 '{columnName}' 의 값 '{stringValue}' 을(를) {targetType} 타입으로 변환할 수 없습니다. 오류: {e.Message}");
                        }
                    }
                }

                // 모든 컬럼에 대한 처리가 끝나면 딕셔너리에 추가
                allRowData.Add(idValue, newRow);
            }
        }

        // 정제된 데이터를 테이블로 변환
        string tableName = csvFile.name;
        return new Table<TKey, TRow>(tableName, idColumName, allRowData);
    }

    private static object ConvertValue(string value, Type targetType)
    {
        value = value.Trim();

        // nullable 이나 비어있는 값에 대한 처리
        if (string.IsNullOrEmpty(value))
        {
            if (targetType.IsValueType && Nullable.GetUnderlyingType(targetType) == null)
            {
                return Activator.CreateInstance(targetType);
            }

            return null;
        }

        // bool 값에 대한 처리 현재는 0, 1 로 표시된 경우만 처리중
        if (targetType == typeof(bool))
        {
            if (int.TryParse(value, out int intValue))
            {
                return intValue != 0;
            }
        }

        // 그 외에는 변환해서 반환하면 끝
        return Convert.ChangeType(value, targetType);
    }
}
  1. CSV 파일을 읽어서 줄과 셀 분리
  2. 헤더를 분석해서 ID 컬럼 위치를 찾고, TRow 의 멤버와 컬럼 이름을 매핑
  3. 데이터 줄 순회하면서 TRow 생성 , CSV 문자열 값을 해당 멤버의 타입에 맞춰 변환하여 할당
  4. ID 를 키로 사용해서 데이터를 찾을 수 있는 Table 객체를 반환

'📖TIL' 카테고리의 다른 글

251210 StringBuilder  (0) 2025.12.10
251210 Github Issue & Github Projects  (0) 2025.12.10
251208 옵저버 패턴 복습  (0) 2025.12.08
251204  (0) 2025.12.04
251203  (0) 2025.12.03
'📖TIL' 카테고리의 다른 글
  • 251210 StringBuilder
  • 251210 Github Issue & Github Projects
  • 251208 옵저버 패턴 복습
  • 251204
DevHoChan
DevHoChan
맨땅에서 시작하는 코딩 도전
  • DevHoChan
    Debugging Life
    DevHoChan
  • 전체
    오늘
    어제
    • 분류 전체보기 (374)
      • 🕹️Game Life (1)
      • 🖥️Computer Science (5)
      • 📖TIL (141)
        • 🔥Projects (16)
        • 💡DevTips (5)
        • 🤔발생한 문제와 해결 (5)
        • 🔮Unity Graphics (5)
        • 🎤Interview (3)
        • ✅CodingTest (9)
      • 🚀Game Release (4)
      • 🧊Unity Basic (58)
        • 📌용어 사전 (1)
        • 에디터&인터페이스 (3)
        • 디버그 (1)
        • 라이프사이클 (4)
        • 게임오브젝트 (4)
        • 프리팹 (1)
        • 오브젝트풀링 (4)
        • 애트리뷰트 (2)
        • 트랜스폼 (4)
        • 물리&충돌 (1)
        • 프레임&델타타임 (4)
        • 코루틴&이벤트 (7)
        • 수학&보정함수 (3)
        • 디자인패턴 (9)
        • UGUI (3)
        • 벡터 ( Vector ) (3)
        • 씬 ( Scene ) (2)
        • 데이터 관리 (2)
      • ⭐C Sharp (99)
        • 📌용어 사전 (1)
        • 📌문법 사전 (6)
        • 메모리 관리 (3)
        • 00. 문법 (17)
        • 01. 변수 (3)
        • 02. 자료형 (2)
        • 03. 연산자 (6)
        • 04. 조건문 (2)
        • 05. 반복문 (2)
        • 06. 배열 (3)
        • 07. 메서드(함수) (7)
        • 08. 열거형 (3)
        • 09. 구조체 (2)
        • 10. 참조 (2)
        • 11. 객체 지향 (11)
        • 12. 델리게이트 (3)
        • 13. 디자인 패턴 (7)
        • 14. LINQ (1)
        • 📂▼자료구조 (2)
        • 15-1. 제네릭 (3)
        • 15-2. 배열 (4)
        • 15-3. 리스트 (2)
        • 15-4. 스택과 큐 (2)
        • 15-5. 딕셔너리 해시테이블 (2)
        • 15-6. 트리와 그래프 (3)
      • 📊Algorithm (16)
        • BigO (2)
        • 정렬 (4)
        • 셔플 (2)
        • 탐색 (6)
        • 최적화 (1)
      • 📝Game Design (16)
      • 🤖​AI Tools (12)
        • AI 리뷰 분석 (6)
        • Player2 (0)
        • 3D 모델링 (1)
        • 2D 스프라이트 (0)
        • 이미지 (2)
        • 사운드 (1)
        • 동영상 (1)
        • 문서 (1)
      • 🌍Network (6)
      • 🌱Github (11)
        • 기본 개념 (7)
        • 명령어 (1)
        • 도구 활용 (1)
      • ⚙️Visual Studio (5)
        • 🔧설치 및 환경설정 (2)
        • ⌨️HotKey (1)
        • 🚨디버깅 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    자료형
    부트캠프
    자료구조
    CodingTest
    기획
    c#
    객체지향
    csharp
    OOP
    게임기획
    til
    문법
    gamedesign
    unity
    메모리관리
    디자인패턴
    유니티
    algorithm
    GitHub
    게임디자인
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
DevHoChan
251209 CSV 파싱
상단으로

티스토리툴바