본문 바로가기

Unity/Coding

JSON in Unity

JSON이란?

  • 서버와 클라이언트 사이에 데이터를 주고받을 때 사용하는 개방형 표준 포맷.
  • 텍스트를 사용하기 때문에 사람이 이해하기 쉽다는 장점을 가지고 있다.
  • JSON은 유니티에서도 다양하게 사용 되는데, 게임에 필요한 데이터를 주고받거나, 게임 진행 상황 또는 설정을 저장할 때 등 광범위하게 사용된다.
  • JSON에서 {}중괄호는 객체를 의미하고 []대괄호는 배열을 나타낸다.
  • XML과 사용처가 비슷하지만 XML은 JSON에 비해 가독성이 떨어지고 데이터를 넣거나 파싱 하는 과정이 까다롭다.
  • JSON은 주석을 제공하지 않는다. 때문에 가독성을 위해 Key의 네이밍에 신경 쓸 필요가 있다.
  • 작은 문법 오류에도 문제가 생길 수 있어서 문법에 오입력이 없도록 신경 쓰자. (JSON 검사기로 테스트)

JSON 코드 예시:

{
  "id": "someID",
  "level": 10,
  "exp": 33.3,
  "hp": 400,
  "items": ["Sword", "Armor", "Hp Potion", "Hp Potion", "Hp Potion"]
}

JSON에서 지원하는 데이터 타입들:

{
    "i":10,
    "f":99.9,
    "b":true,
    "str":"JSON Test"
    "iArray":[1,1,2,3,5,8,12,34],
    "iList":[0,2,4,6,8],
    "fDictionary":
    {
        "PIE":3.14159274,
        "Epsilon":1.401298E-45,
        "Sqrt(2)":1.41421354
    },
    "iVector":
    {
        "x":3,
        "y":2
    }
}

 


Newtonsoft JSON Library

  • 외부 라이브러리인 Newtonsoft JSON Library를 사용하기 위해서 using Newtonsoft.Json을 선언해 준다.
  • 테스트 용도로 사용할 클래스를 정의하자.
  • JSON이 담을 수 있는 대부분의 타입을 멤버 변수로 추가하였다.
  • Int, Float, Bool, String과 같이 기본적인 데이터 타입부터 배열, List, Dictionary와 같은 컨테이너 타입,
  • 그리고 사용자가 직접 정의한 클래스 형식도 한 클래스에 묶어서 JSON 데이터 형식으로 만들 수 있다.
public class JsonTestClass
{
    public int i;
    public float f;
    public bool b;
    public string str;
    public int[] iArray;
    public List<int> iList = new List<int>();
    public Dictionary<string, float> fDictionary = new Dictionary<string, float>();
    public IntVector2 iVector;

    public class IntVector2
    {
        public int x;
        public int y;

        public IntVector2(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }
}

 

생성자에서 값을 자동으로 초기화시켜 보았다.

public JsonTestClass()
{
    i = 10;
    f = 99.9f;
    b = true;
    str = "JSON Test string!";
    iArray = new int[] { 1, 1, 2, 3, 5, 6, 12, 21, 34, 55 };

    for (int idx = 0; idx < 5; idx++)
    {
        iList.Add(2 * idx);
    }

    fDictionary.Add("PIE", Mathf.PI);
    fDictionary.Add("Epsilon", Mathf.Epsilon);
    fDictionary.Add("Squrt(2)", Mathf.Sqrt(2));

    iVector = new IntVector2(3, 2);
}

 

모든 값을 출력하는 Print 함수도 정의해 준다.

public void Print()
{
    Debug.Log($"i = {i}");
    Debug.Log($"f = {f}");
    Debug.Log($"b = {b}");
    Debug.Log($"str = {str}");

    for (int idx = 0; idx < iArray.Length; idx++)
    {
        Debug.Log(string.Format("iArray[{0}] = {1}", idx, iArray[idx].ToString()));
    }
    for (int idx = 0; idx < iList.Count; idx++)
    {
        Debug.Log(string.Format("iList[{0}] = {1}", idx, iList[idx].ToString()));
    }
    foreach (var data in fDictionary)
    {
        Debug.Log(string.Format("iDictionary[{0}] = {1}", data.Key, data.Value));
    }

    Debug.Log("iVector = " + iVector.x + ", " + iVector.y);
}

 

자, 이제 Start에서 JSON 데이터를 테스트해보자.

하나의 오브젝트를 문자열인 JSON 데이터로 만드는 과정은 매우 간단하다.

JsonConvert.SerializeObject()를 통해서 오브젝트를 매게 변수에 넣어주면 끝이다.

이 과정을 '직렬화'라고 한다.

JsonTestClass jsonTest1 = new JsonTestClass();
string jsonData = JsonConvert.SerializeObject(jsonTest1);

 

그럼 이제 반대로 문자열이 된 JSON 데이터를 오브젝트로 변환해 보자.

JSON 데이터를 다시 오브젝트로 바꿀 때는, 어떤 오브젝트로 변환하는지 명시적으로 함수에 알려야 한다.

만약 JSON 데이터가 가진 구조가 함수에 알려준 클래스의 구조와 다르다면 변환 도중에 에러가 발생한다.

오브젝트 변환 후 아까 만든 Print() 함수를 통해 데이터를 확인해 보자.

JsonTestClass jsonTest2 = JsonConvert.DeserializeObject<JsonTestClass>(jsonData);
jsonTest2.Print();

 

주의 사항

  • MonoBehaviour를 상속받는 오브젝트를 Serialize 할 경우에 Self Referencing Loop, 즉 자기 참고 루프에 빠질 수 있게 때문에 주의가 필요하다.
  • MonoBehaviour를 상속 받는 오브젝트를 Serialize 하는 대신, 필요한 property를 클래스로 묶어서 해당 클래스만 Serialize 하거나 이후에 설명할 유니티가 기본 제공하는 JsonUtility를 통해 Serialize 하도록 하자.
  • Vector3 역시 자기 참조 루프에 빠지게 되니 따로 Serialize용 Vector 클래스를 만들어서 사용하도록 하자.
public class SerializableVector3
{
    public float x;
    public float y;
    public float z;
}

JSON Utility

유니티에서 기본 제공되는 Json Utility에 대해서 알아보자.

JsonUtility.ToJson() 함수를 통해 간단하게 오브젝트를 JSON 데이터로 만들 수 있다.

반대로 JSON 데이터에서 오브젝트로 만들 때는 JsonUtility.FromJson<objectType>(jsonData); 함수를 사용한다.

var jTest1 = new JsonTestClass();
var jsonData = JsonUtility.ToJson(jTest1);
Debug.Log(jsonData);

var jTest2 = JsonUtility.FromJson<JsonTestClass>(jsonData);
jTest2.Print();

 

MonoBehaviour를 상속받는 클래스의 Serialization

MonoBehaviour를 상속 받는 클래스를 Serialize 할 때는 GetComponent 등으로 직접 가져온 클래스의 오브젝트로 Serialize 해야 한다.

GameObject obj = new GameObject();
obj.AddComponent<TestMono>();
string jsonData = JsonUtility.ToJson(obj.GetComponent<TestMono>());

MonoBehaviour를 상속 받는 오브젝트의 JSON 데이터를 다시 오브젝트로 만들려면, 같은 형태의 오브젝트를 생성한 뒤 Deserialize해야 한다.

 

JsonUtility.FromJsonOverwrite() 함수는 JSON 데이터를 다시 오브젝트로 변환할 때, 새로운 오브젝트를 만들지 않고 기존에 있는 오브젝트 클래스의 변수 값을 덮어씌우는 처리를 한다.

 

이 기능을 이용하면 게임 내의 오브젝트를 JSON 데이터로 저장해서 게임 진행 상황을 저장하고 불러올 수 있다.

GameObject obj2 = new GameObject();
JsonUtility.FromJsonOverwrite(jsonData, obj2.AddComponent<TestMono>());

 

주의 사항

  • 다만 JsonUtility의 단점은 기본적인 데이터 타입과 배열, 리스트에 대한 Serialize만 지원한다.
  • 그래서 위에 Print() 함수로 출력된 텍스트를 보면 딕셔너리나 직접 정의한 클래스에 대한 데이터는 빠진 것을 알 수 있다.
  • 직접 생성한 클래스의 경우에는 [System.Serializable] 어트리뷰트를 붙여줘야만 JSON 데이터로 변환된다.
  • 딕셔너리를 사용하려면 외부 라이브러리를 사용해야 한다.

JSON 데이터 파일로 저장하기

  1. 먼저 JsonSaveLoader 테스트 클래스를 생성해 보자. 
  2. Start 함수에서 파일을 저장할 경로와 파일 이름으로 FileStream을 만들어준다. (보통 JSON 파일은 .json 확장자를 가진다)
  3. 문자열인 JSON 데이터를 Encoding.UTF8의 GetBytes 함수로 byte 배열로 만들어준다.
  4. 이렇게 만든 데이터를 FileStream에 써준다.
using System.IO;
using System.Text;
using UnityEngine;
using Newtonsoft.Json;

public class JsonSaveLoader : MonoBehaviour
{
    private void Start()
    {
        FileStream stream = new FileStream(Application.dataPath + "/test.json", FileMode.OpenOrCreate);
        JsonTestClass jTest1 = new JsonTestClass();
        string jsonData = JsonConvert.SerializeObject(jTest1);
        byte[] data = Encoding.UTF8.GetBytes(jsonData);
        stream.Write(data, 0, data.Length);
        stream.Close();
    }
}

 

이렇게 저장된 JSON 파일을 읽어 들이는 방법도 살펴보자.

이번에도 FileStream을 생성하되, FileMode를 Open으로 설정한다.

그다음에 이 문자열을 Deserialize해준다.

FileStream stream = new FileStream(Application.dataPath + "/test.json", FileMode.Open);
byte[] data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
stream.Close();
string jsonData = Encoding.UTF8.GetString(data);
JsonTestClass jTest2 = JsonConvert.DeserializeObject<JsonTestClass>(jsonData);
jTest2.Print();

 

'Unity > Coding' 카테고리의 다른 글

Addressables in Unity  (0) 2023.01.11
PlayerPrefs  (0) 2022.12.28