[Unity] JSON으로 데이터 직렬화 및 파일 입출력하기

kkado·2024년 9월 28일
0

Unity

목록 보기
2/2
post-thumbnail

데이터를 JSON 형태로 저장 및 로드할 수 있는 기능을 구현해보자.

먼저 직렬화할 데이터 타입을 만들어야 하는데 여기서 중요한 점은 위에 [Serializable] 속성을 추가하여 직렬화 가능하게 만들어야 한다는 것이다. 아래 '스크립트 직렬화' Unity 문서에서 자세한 정보를 확인할 수 있다.

지원되는 타입: JSON 시리얼라이저 API는 MonoBehaviour 서브클래스, ScriptableObject 서브 클래스, 또는 [Serializable] 속성이 있는 일반 클래스/구조체를 지원합니다. 링크

(배열 형태가 아닌) 단일 데이터의 직렬화/역직렬화

단일 데이터는 유니티에 내장되어 있는 JsonUtility 클래스를 통해 쉽게 직렬화/역직렬화 할 수 있다.

[Serializable]
public class Player
{
    public string playerId;
    public string playerLoc;
    public string playerNick;
}


Player playerInstance = new Player();
playerInstance.playerId = "8484239823";
playerInstance.playerLoc = "Powai";
playerInstance.playerNick = "Random Nick";

//Convert to JSON
string playerToJson = JsonUtility.ToJson(playerInstance, true);
Debug.Log(playerToJson);

뒤에 true 를 붙여 주면 아래와 같이 깔끔하게 포맷팅하여 데이터를 직렬화할 수 있다.

{
    "playerId": "8484239823",
    "playerLoc": "Powai",
    "playerNick": "Random Nick"
}

역직렬화는 JsonUtility.FromJson() 함수를 이용할 수 있다.

string jsonString = "{\"playerId\":\"8484239823\",\"playerLoc\":\"Powai\",\"playerNick\":\"Random Nick\"}";
Player player = JsonUtility.FromJson<Player>(jsonString);
Debug.Log(player.playerLoc);

string jsonString = "{\"playerId\":\"8484239823\",\"playerLoc\":\"Powai\",\"playerNick\":\"Random Nick\"}";
Player player = (Player)JsonUtility.FromJson(jsonString, typeof(Player));
Debug.Log(player.playerLoc);

타입을 템플릿 형태로 <> 꺾쇠에 담아 전달하는 방법, 또는 두 번째 인자로 전달하는 방법 모두 가능하다.

배열 형태의 데이터의 직렬화/역직렬화

Json 데이터는 기본적으로 키-밸류 형식을 가져야 하므로 배열 자체를 바로 포함할 수 없고, 어떤 키 값을 하나 만들고 거기에 대응되는 밸류에 포함시키는 방식을 사용한다.

이 작업을 간편하게 할 수 있는 스니펫을 사용할 수 있다.

public static class JsonHelper
{
    public static T[] FromJson<T>(string json)
    {
        Wrapper<T> wrapper = JsonUtility.FromJson<Wrapper<T>>(json);
        return wrapper.Items;
    }

    public static string ToJson<T>(T[] array)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.Items = array;
        return JsonUtility.ToJson(wrapper);
    }

    public static string ToJson<T>(T[] array, bool prettyPrint)
    {
        Wrapper<T> wrapper = new Wrapper<T>();
        wrapper.Items = array;
        return JsonUtility.ToJson(wrapper, prettyPrint);
    }

    [Serializable]
    private class Wrapper<T>
    {
        public T[] Items;
    }
}

이 코드는 기존의 배열 형식의 데이터를 Wrapper 라는 새로운 데이터에 존재하는 Items 키의 밸류로 만들어서 하나의 Json 파일로 만들 수 있게 한다.

이제 이 JsonHelper를 사용해서 간편하게 직렬화할 수 있다.

Player[] playerInstance = new Player[2];

playerInstance[0] = new Player();
playerInstance[0].playerId = "8484239823";
playerInstance[0].playerLoc = "Powai";
playerInstance[0].playerNick = "Random Nick";

playerInstance[1] = new Player();
playerInstance[1].playerId = "512343283";
playerInstance[1].playerLoc = "User2";
playerInstance[1].playerNick = "Rand Nick 2";

//Convert to JSON
string playerToJson = JsonHelper.ToJson(playerInstance, true);
Debug.Log(playerToJson);

역직렬화도 쉽게 가능하다.

string jsonString = "{\r\n    \"Items\": [\r\n        {\r\n            \"playerId\": \"8484239823\",\r\n            \"playerLoc\": \"Powai\",\r\n            \"playerNick\": \"Random Nick\"\r\n        },\r\n        {\r\n            \"playerId\": \"512343283\",\r\n            \"playerLoc\": \"User2\",\r\n            \"playerNick\": \"Rand Nick 2\"\r\n        }\r\n    ]\r\n}";

Player[] player = JsonHelper.FromJson<Player>(jsonString);
Debug.Log(player[0].playerLoc);
Debug.Log(player[1].playerLoc);

json string을 Wrapper 형식, 즉 'Items' 라는 키값이 있는 형식으로 변형한 후 리턴한다.

만약 갖고 있는 json string이 앞에 Items: 라는 키값이 아니라면, 수동으로 앞에 Items라는 키값을 가지는 새로운 json으로 변형한 후 사용할 수 있다.

string fixJson(string value)
{
    value = "{\"Items\":" + value + "}";
    return value;
}
string jsonString = fixJson(yourJsonFromServer);
Player[] player = JsonHelper.FromJson<Player>(jsonString);

파일 입출력

이제 런타임에서 만든 Json 파일을 로컬에 저장해보는 작업이다.
먼저 간단한 데이터 형식을 만든다.

using System;

[Serializable]
public class InputEntry
{
    public int A;
    public int B;

    public InputEntry(int A, int B)
    {
        this.A = A;
        this.B = B;
    }
}

FileHandler 라는 이름의 static class를 하나 만들고 모든 기능을 구현하기로 하고, 먼저 입력부터 진행해보자

public static class FileHandler
{
    public static void SaveToJSON<T>(List<T> listToSave, string fileName)
    {
        string filePath = GetFilePath(fileName);
        Debug.Log(filePath);
        string serializedData = JsonHelper.ToJson<T>(listToSave.ToArray(), true);
        WriteToFile(filePath, serializedData);
    }

    private static string GetFilePath(string fileName)
    {
        return Application.persistentDataPath + "/" + fileName;
    }
    
    private static void WriteToFile(string filePath, string contentToSave)
    {
        FileStream fileStream = new FileStream(filePath, FileMode.Create); // 새 파일스트림 생성. 만약 이미 존재한다면 덮어쓰기

        using (StreamWriter writer = new StreamWriter(fileStream))
        {
            writer.Write(contentToSave);
        }
    }
}

SaveToJSON() 함수는 이전에 보았던 JsonHelper를 통해 Json 형식의 파일을 string으로 만드는 과정을 담고 있다.
GetFilePath() 함수는 데이터를 저장할 경로를 지정하는 것으로, 파일 위치는 상관없으나 Application.persistentDataPath 에 저장한다.
WriteToFile은 실질적으로 파일에 입력하는 과정으로, 파일스트림을 만들고 StreamWriter를 만들어서 진행한다.

그리고 버튼을 하나 만들어 런타임에서 랜덤한 숫자쌍을 통해 InputEntry 를 만들도록 한다. 그리고 이것을 FileHandler에 전달한다.

public class InputHandler : MonoBehaviour
{
    [SerializeField] private Button btn;
    private List<InputEntry> _entries = new List<InputEntry>();

    public void AddToEntries()
    {
        _entries.Add(new InputEntry(Random.Range(0, 100), Random.Range(0, 100)));
        FileHandler.SaveToJSON<InputEntry>(_entries, "JsonSavedData.json");
    }
}

버튼을 클릭하면 위 그림과 같이 로그가 찍힌 것을 볼 수 있고 해당 위치로 가서 파일을 열어 보면

Json 형태로 파일이 저장된 것을 볼 수 있다.

물론 버튼을 여러 번 누르면 여러 데이터가 저장된다.

파일로부터 읽어오는 과정도 입력과 비슷하게 진행된다.

    public static List<T> ReadFromJSON<T>(string fileName)
    {
        string content = ReadFromFile(GetFilePath(fileName));
        if (string.IsNullOrEmpty(content) || content == "{}")
        {
            return new List<T>();
        }

        return JsonHelper.FromJson<T>(content).ToList(); // System.Linq 임포트 필요
    }
    
    private static string ReadFromFile(string filePath)
    {
        if (File.Exists(filePath))
        {
            using (StreamReader reader = new StreamReader(filePath))
            {
                string content = reader.ReadToEnd();
                return content;
            }
        }

        return "";
    }

이러면 기존에 만들어졌던 파일에다가 내용을 추가할 수 있다.

public class InputHandler : MonoBehaviour
{
    [SerializeField] private Button btn;
    private List<InputEntry> _entries = new List<InputEntry>();

    private void Start()
    {
        _entries = FileHandler.ReadFromJSON<InputEntry>("JsonSavedData.json");
    }

    public void AddToEntries()
    {
        _entries.Add(new InputEntry(Random.Range(0, 100), Random.Range(0, 100)));
        FileHandler.SaveToJSON<InputEntry>(_entries, "JsonSavedData.json");
    }
}

시작할 때 기존의 파일을 읽어오고 거기에 추가하여 저장할 수 있도록 한다.

예를 들어 79, 84 쌍의 데이터를 먼저 만들어 놓고, 실행 후 버튼을 눌러 새 데이터를 넣으면,

  1. 84 밑에 새로운 엔트리를 추가한 새 entries 배열을 가지고 Json 파일을 만들어 새로 저장한 결과를 볼 수 있다.

참고 자료

https://www.youtube.com/watch?v=KZft1p8t2lQ
https://stackoverflow.com/questions/36239705/serialize-and-deserialize-json-and-json-array-in-unity

profile
베이비 게임 개발자

0개의 댓글