내일배움캠프/TIL

직렬화 특강 정리 - CSV, XML, Json 등

서보훈 2024. 11. 4. 20:36

직렬화

객체를 바이트 배열로 바꾸어 저장하는것

프로그램의 기반

유니티는 직렬화 데이터들의 집합 이라고 할 수 있음

  • Scene,SO, meta, prefab 이 모두 직렬화 데이터
  • 메모장을 통해 prefab 등의 파일을 열어볼경우, YAML 형태로 직렬화 되어있는것을 확인할 수 있음


역 직렬화

저장한 데이터를 바이트 배열으로 바꾸어, 다시 객체로 변환하는것


직렬화, 역직렬화의 과정

  1. 코드들이 메모리에 산재됨
  2. 재시작시 사용되는 메모리의 위치가 변경, 저장된 메모리는 이전 위치에 유지됨
  3. 흩어져있는 내용을 정리해서 나열 → 직렬화
  4. 나열된 값을 원래 자리에 되돌림 → 역직렬화

직렬화를 한 뒤, 나중에 역직렬화가 가능함

→ 직렬화를 통해 저장/통신 이 가능함

 

초기 직렬화

0, 1 으로 데이터를 나열하여 사용 (바이트 배열)

→ 컴퓨터는 이해하지만, 사람은 이해하기 어려움(개발이 어려움)

→ 사람이 읽을 수 있는 방식으로 직렬화

 


CSV

  • Comma Separated Value
  • 컴마(,) 로 값을 나누어서 저장하는 방식
  • 엑셀에서 기본 지원

사용법

  • 엑셀에서 CSV UTF-8 으로 파일 저장
  • 유니티에 넣어서 저장된 값 사용 가능
  • ',' 와 엔터로 구분됨

 

역 직렬화

  • CSV 파일을 Resources 폴더에 저장
  • CSV 파일을 TextAsset 형태로 로드
  • TextAsset을 string 값으로 변환 (변수명.text.TrimEnd())
    • TrimEnd 를 하는 이유 : 끝부분에 \n이 존재하며, 이 부분이 공백으로 저장되어있기 때문에 마지막의 공백을 삭제해줄 필요가 있음
  • string 값으로 변환된 TextAsset을 '\n' 으로 나누기 (.Split('\n'))
  • \n 으로 나눠진 값을 다시 ',' 으로 나누기 (.Split(','))
public class CSVTest : MonoBehaviour
{
    private Dictionary<string, string> itemData = new Dictionary<string, string>();

    private void Start()
    {
        TextAsset csvData = Resources.Load<TextAsset>("TestCSV");
        var data = csvData.text.TrimEnd();

        DeSerialization(data);

        foreach(var item in itemData)
        {
            Debug.Log($"{item.Key} : {item.Value}");
        }
    }

    private void DeSerialization(string originData)
    {
        var rowData = originData.Split('\n');

        for(int i = 0; i < rowData.Length; i++)
        {
            var data = rowData[i].Split(',');
            itemData[data[0]] = data[1];
        }
    }
}

 

단점

  • 저장된 값이, 어떤 값인지 알기 어려움

XML, json, YAML

Key : Value 값으로 직렬화 되어있어 어떤값인지 알아보기 쉬움

 


XML

Extensible Markup Language

<루트노드>
    <자식노드>
        <요소> 요소내용 </요소>
    </자식노드>
</루트노드>

이러한 형태로 구성됨

사용

쓰기 (직렬화)

  • XmlDocument 를 통해 작성 가능
  • XmlDocument 객체를 생성한 뒤, 노드를 만들어 연결해주는 형태
void CreateXML(Player p)
{
    XmlDocument xmlDoc = new XmlDocument();
    
    //헤더 (없어도 무관함)
    //xmlDoc.AppendChild(xmlDoc.CreateXmlDeclaration());

    // 루트 노드
    XmlNode root = xmlDoc.CreateNode(XmlNodeType.Element, "GameData", string.Empty);
    xmlDoc.AppendChild(root);

    // 자식 노드
    XmlNode child = xmlDoc.CreateNode(XmlNodeType.Element, "PlayerData", string.Empty);
    root.AppendChild(child);

    XmlElement nickname = xmlDoc.CreateElement("nickname");
    nickname.InnerText = p.nickname;
    child.AppendChild(nickname);

    XmlElement lv = xmlDoc.CreateElement("lv");
    lv.InnerText = p.lv.ToString();
    child.AppendChild(lv);

    XmlElement hp = xmlDoc.CreateElement("hp");
    hp.InnerText = p.hp.ToString();
    child.AppendChild(hp);

    xmlDoc.Save("./Assets/Resources/GameDatas.xml");

    AssetDatabase.Refresh(); // 에디터에서만 써야함.
}

root 가 루트 노드가 된 뒤, child가 root에 연결되고, child에 nickname, lv, hp 를 연결한 형태로 Xml 이 만들어짐

 

읽기 (역직렬화)

  • Resources 폴더에 Xml 파일을 넣어서 사용
  • XmlDocument 클래스의 객체를 선언한 뒤, Resources.Load<TextAsset> 으로 읽어들인 Xml 파일을 객체에 지정
  • 지정한 객체의 노드를 선택하여 값을 읽어들일 수 있음
void LoadXML()
{
    var t = Resources.Load<TextAsset>("GameDatas");

    XmlDocument xmlDoc = new XmlDocument();
    xmlDoc.LoadXml(t.text);

    XmlNodeList nodes = xmlDoc.SelectNodes("GameData/PlayerData");

    XmlNode playerData = nodes[0];

    player.nickname = playerData.SelectSingleNode("nickname").InnerText;
    player.lv = int.Parse(playerData.SelectSingleNode("lv").InnerText);
    player.hp = float.Parse(playerData.SelectSingleNode("hp").InnerText);
}

https://docs.unity3d.com/kr/2019.4/Manual/JSONSerialization.html

 

JSON 직렬화 - Unity 매뉴얼

JsonUtility 클래스를 사용하여 Unity 오브젝트를 JSON 포맷으로 상호 전환할 수 있습니다. 예를 들어 JSON 직렬화를 사용하여 웹 서비스와 상호작용하거나, 데이터를 텍스트 기반 포맷으로 쉽게 패킹

docs.unity3d.com

 

단점

  • 규칙이 엄격함
  • 노드와 요소를 만들때, 항상 쌍으로 존재하며 열었으면 닫아야함

json

  • 사실상 기본값
  • 편하게 사용하기위한 관련기능이 많음
  • Newtonsoft, LitJson, JsonUtility 등이 있지만, 유니티에선 JsonUtility를 사용 (성능이 가장 좋음)
  • JsonUtility 의 단점 : 확장성이 좋지 않음

직렬화 규칙

  • public/ SerializeField 속성이 있어야함
  • static이 아니여야함

커스텀 직렬화

지원하지 않는 항목을 직렬화할 때 사용

 

사용

쓰기 (직렬화)

public class Character : MonoBehaviour
{
    public UserData userData;

    public void Save()
    {
        var saveData = JsonUtility.ToJson(userData);

        Debug.Log(saveData);

        Debug.Log(Application.persistentDataPath + "/UserData.txt");
        //저장 - string 값으로 변환하여 저장
        File.WriteAllText(Application.persistentDataPath + "/UserData.txt", saveData);
    }
}

[System.Serializable]
public class UserData
{
    public string nickname;
    public int lv;
    public float hp;

    public List<string> friends = new List<string>();

    public Inventory inven;
}

[System.Serializable]
public class Inventory
{
    public int size;
    public List<Item> items = new List<Item>();
}

[System.Serializable]
public class Item
{
    public int itemId;
}

 

읽기 (역직렬화)

public void Load()
{
    string loadData = File.ReadAllText(Application.persistentDataPath + "/UserData.txt");

    userData = JsonUtility.FromJson<UserData>(loadData);
}

 

※Application.persistentDataPath : 환경에 적합한 경로를 지정

 

주의사항

  • json 을 사용하여 데이터 저장시, json 파일에 접근이 가능하면 저장한 데이터를 변조 할 수 있음
  • 서버에 데이터를 저장하지 않을경우, 암호화를 하여 데이터 변조를 막아줄 필요가 있음
  • AES 등을 사용하여 직렬화시 암호화를 해줄 필요가 있음

스크립터블 오브젝트 SO

  • 저장할 내용을 SO 오브젝트를 만들어서 저장

특징, 문제점

  • json, Xml 등에서 저장할 수 없는 내용을 저장할 수 있음 (Unity 컴포넌트 정보를 저장할 수 있음)
  • 에디터 내에서는 저장에 문제가 없지만, 빌드를 할경우 재시작시 빌드했던 수치로 값이 초기화됨
  • 많은 게임오브젝트가 같은 내용을 사용하더라도 하나의 SO를 사용하기 때문에 최적화에 좋음

※ 변하지 않는 값을 SO를 사용하여 저장, 사용