Priv's Blog

세션 간의 데이터 지속성 (JsonUtility & NewtownSoft Json) 본문

Dev. Study Note/Unity

세션 간의 데이터 지속성 (JsonUtility & NewtownSoft Json)

Priv 2024. 6. 14. 19:02


 

 

1. JsonUtility

Unity는 기본적으로 Json을 이용해 데이터를 외부에 저장할 수 있는 기능을 지원합니다.

Unity Learn에서는 세션 간의 데이터 지속성으로 설명하고 있으며, 직렬화/역직렬화 개념을 기반으로 합니다.


 

Manage scene flow and data: Implement data persistence between sessions

출처 Implement data persistence between sessions - Unity Learn In this tutorial, you’ll write code to save and load the color that the user selects so that it persists between sessions of the application. By the end of this tutorial, you will be able t

arainablog.tistory.com


다만 Unity에서 제공하는 JsonUtility는 가장 기본적인 기능만 제공하고 있어 직렬화 가능한 범위가 매우 제한적입니다.

Unity Learn에서는 아래와 같이 서술하고 있습니다.


- JsonUtility의 한계

Unity 엔진의 JsonUtility는 성능과 단순함에 초점을 맞추어 설계되었기 때문에 몇 가지 한계점을 가지고 있습니다.
JsonUtility는 원시 자료형(Primitive Types), 배열(Array), 리스트(List), 딕셔너리(Dictionary)와 함께 사용할 수 없습니다.


 


 

2. XML

JsonUtility를 이용한 직렬화/역직렬화 기능이 마음에 들지 않는다면, XML을 사용하는 방법도 있습니다.


 

[Unity3D] 유니티에서 XML 사용하기

유니티에서 XML 사용하기 게임을 제작할 때에, 여러 가지 데이터들을 저장하고 불러와야하는 경우가 많다. 게임 진행 상황을 저장하는 경우도 있을 수 있고, 맵이 랜덤으로 생성되는 게임의 경우

wergia.tistory.com


다만 XML을 사용하는 방식은 가독성이 매우 떨어지며, Json보다 데이터를 넣고 꺼내는, 파싱(Parsing) 과정이 까다롭습니다.

Json의 뛰어난 가독성, 편리하고 직관적인 문법, 간단한 파싱 과정을 모두 포기하기에는 부담이 클 수밖에 없습니다.

 


 

3. Newtonsoft JSON

Unity에서 제공하는 JsonUtility, XML도 마음에 들지 않는다면, 타사에서 제공하는 Json 라이브러리를 적용해 사용하면 됩니다.

라이브러리의 종류에 따라 성능과 특징이 모두 다르겠지만, 그중에서 가장 많이 사용하는 라이브러리는 Newtonsoft Json 라이브러리입니다.

이 라이브러리는 JsonUtility에서 지원하지 않던 자료형에 대한 직렬화/역직렬화도 지원합니다.

특히 딕셔너리의 직렬화/역직렬화도 지원하기 때문에 상당히 유용합니다.

사용법은 JsonUtility와 크게 다르지 않지만, 라이브러리가 다르기 때문에 사용되는 키워드에서 차이가 존재합니다.

using System;
using System.IO;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;
using UnityEditor;

public class GameControlSaveLoad : GameControlSingleton<GameControlSaveLoad> {
    private string fileName;
    private string filePath;


    private void Init() {
        this.fileName = DateTime.Now.ToString("yyyy-MM-dd");
        this.filePath = Application.persistentDataPath;
    }

    private void Awake() {
        Init();
    }

    // Obj -> Json
    public string ObjectToJson(object obj) {
        return JsonConvert.SerializeObject(obj);
    }

    // Json -> Obj
    public T JsonToObject<T>(string jsonData) {
        return JsonConvert.DeserializeObject<T>(jsonData);
    }

    // Json -> File
    public void CreateJsonFile(string jsonData) {
        FileStream fileStream = new FileStream($"{this.filePath}/{this.fileName}.json", FileMode.Create);
        byte[] data = Encoding.UTF8.GetBytes(jsonData);
        
        fileStream.Write(data, 0, data.Length);
        fileStream.Close();
    }
    
    // Json -> Obj
    public T LoadJsonFile<T>() {
        if (!File.Exists($"{this.filePath}/{this.fileName}.json")) {
            throw new FileNotFoundException();
        }
        
        FileStream fileStream = new FileStream($"{this.filePath}/{this.fileName}.json", FileMode.Open);
        byte[] data = new byte[fileStream.Length];

        fileStream.Read(data, 0, data.Length);
        fileStream.Close();

        var jsonData = Encoding.UTF8.GetString(data);

        return JsonConvert.DeserializeObject<T>(jsonData);
    }
}

제공되는 메서드들은 JsonConvert 클래스를 통해 접근할 수 있으며, 직렬화/역직렬화를 위해서는 아래와 같이 작성해 사용하면 됩니다.

JsonConvert.SerializeObject(obj);
JsonConvert.DeserializeObject<T>(jsonData);

역직렬화의 경우, Json 데이터를 객체로 전환해야 하므로, 제네릭 형태로 구성되어 있습니다.

 


 

4. SerializableDictionary

Newtonsoft의 JSON 라이브러리와 함께 사용하기 좋은 에셋을 하나 뽑으라면, SerializableDictionary를 뽑을 수 있습니다.

일반적으로 Unity 상에서 List는 직렬화가 가능해도, Dictionary는 직렬화가 불가능하다고 알려져 있습니다.

하지만 제네릭의 특성을 사용하면 간접적으로나마 Dictionary도 직렬화가 가능합니다.

<Tkey, Tvalue> 형식을 사용하는 제네릭 클래스를 생성하고 Key와 Value 데이터를 각각의 List로 저장한 뒤, 제네릭 클래스 타입의 객체 형태로 묶어서 직렬화하는 것입니다.

물론, 아이디어를 직접 구현하여 개발에 적용하는 것도 좋겠지만, 아쉽게도(?) 이를 벌써 구현해 둔 에셋이 있습니다.


 

SerializableDictionary | 기능 통합 | Unity Asset Store

Use the SerializableDictionary from Mathieu Le Ber on your next project. Find this integration tool & more on the Unity Asset Store.

assetstore.unity.com


위 에셋을 사용하여 Unity 에디터의 Inspector 창에서 직접 딕셔너리의 값에 접근할 수 있도록 응용하는 방법도 존재합니다.



 


 

5. JsonSerializationException: Self referencing loop detected


 

 

JsonSerializationException: Self referencing loop detected

Hello, I 'm using the Newtonsoft.json to make some saving loading function. So far it was working, now I get this error : JsonSerializationException:...

forum.unity.com

 

[Unity3D] 유니티에서 JSON 사용하기(Newtonsoft JSON)

유니티에서 JSON 사용하기(Newtonsoft JSON) 작성 기준 버전 :: 2018.3.1f1 JSON은 웹이나 네트워크에서 서버와 클라이언트 사이에서 데이터를 주고 받을 때 사용하는 개방형 표준 포멧으로, 텍스트를 사용

wergia.tistory.com


다만 Newtonsoft의 JSON도 완벽하지는 않습니다.

Unity 엔진으로 게임을 개발할 때, 대부분의 클래스는 Monobehaviour 클래스를 상속합니다.

Start( ), Update( ) 메서드처럼 우리에게 친숙한 개념들을 사용하기 위해서, 게임 오브젝트에 스크립트 파일을 컴포넌트로 연결하기 위해서는 Monobehaviour를 상속해야 합니다.

이 때문에 Unity 엔진 상에서 스크립트를 새로 생성하면 기본적으로 이 클래스를 상속한 상태로 클래스가 생성됩니다.

하지만 Newtonsoft의 JSON 라이브러리를 이용하여 Monobehaviour를 상속한 클래스를 직렬화/역직렬화하려고 시도하면 아래와 같은 에러가 발생합니다.

JsonSerializationException: Self referencing loop detected for property ~

이는 gameObject가 gameObject를 호출할 수 있는 순환 구조 때문에 발생하는 것이며, 알려져 있는 해결책들도 사실상 임시방편책에 가깝습니다.

이 때문에 Newtonsoft의 JSON 라이브러리로 직렬화/역직렬화하고 싶은 클래스는 Monobehaviour를 상속하지 않는 상태로 만들거나, JSON에 저장하고 싶은 프로퍼티만 별도의 클래스로 묶어서 직렬화해야 합니다.

다만 프로퍼티만 묶어서 직렬화하는 방법은 접근성 문제 때문에 다루기가 까다롭거나 클래스 안에 클래스가 존재하는 방식이므로 가독성을 해칠 수 있어서 가급적이면 JsonUtility와 함께 사용하거나, Monobehaviour 상속을 포기하는 것을 권장합니다.

 


 


수고하셨습니다!


Comments