2017년 12월 26일 화요일

Unity - Local DB - C# - JsonUtility



JsonUtility

유니티 5.3 이상부터 JsonUtility를 지원한다. 외부 JSON 라이브러리가 필요 없다.

< JsonUtility 멤버 함수 >
  1. FromJson : JSON을 오브젝트로 변환한다. 오브젝트를 생성한다.
  2. FromJsonOverwrite : JSON을 오브젝트로 변환한다. 오브젝트를 생성하지 않고 값을 변경한다.
  3. ToJson : 오브젝트의 public 필드를 JSON으로 변환한다.
< JsonUtility 직렬화 >
  1. 클래스 [Serializable] 속성
  2. private 멤버 변수에 [SerializeField]의 특성
  3. public 멤버 변수

1. 기본 사용법

Enemy에서는 public을 사용하지 않고 SerializeField를 이용했다.
코드가 복잡하지 않기 때문에 자세한 설명은 생략한다.

using UnityEngine;
using System;
using System.Collections.Generic;

[Serializable]
public class Enemy
{
    [SerializeField] string name;
    [SerializeField] List<string> skills;
  
    public Enemy(string name, List<string> skills)
    {
        this.name = name;
        this.skills = skills;
    }
}

//Test
string jsonStr = JsonUtility.ToJson(new Enemy("오거", new List<string>() { "물리공격", "마법" }));
Debug.Log(jsonStr);

Enemy enemy0 = JsonUtility.FromJson<Enemy>(jsonStr);

Enemy enemy1 = new Enemy("empty", new List<string>() { });
JsonUtility.FromJsonOverwrite(jsonStr, enemy1);

결과)
jsonStr의 출력 결과는 다음과 같다.
 {"name":"오거","skills":["물리공격","마법"]}
enemy1 값이 오거, 물리공격, 마법으로 변경된다.

2. List 직렬화

다음과 같이 List를 Json으로 변환하면 빈 문자열만 변환한다.
        var enemies = new List<Enemy>();
        enemies.Add(new Enemy("오거", new List<string>() { "물리공격", "마법" }));
        enemies.Add(new Enemy("트롤", new List<string>() { "공격", "회복", "부활" }));

        string emptyStr = JsonUtility.ToJson(enemies);

List는 별도로 직렬화 시켜 주어야 한다.

[Serializable]
public class Serialization<T>
{
    [SerializeField]
    List<T> target;
    public List<T> ToList() { return target; }

    public Serialization(List<T> target)
    {
        this.target = target;
    }
}

//Test
var enemies = new List<Enemy>();
enemies.Add(new Enemy("오거", new List<string>() { "물리공격", "마법" }));
enemies.Add(new Enemy("트롤", new List<string>() { "공격", "회복", "부활" }));

string str = JsonUtility.ToJson(new Serialization<Enemy>(enemies));
Debug.Log(str);

List<Enemy> retEnemies = JsonUtility.FromJson<Serialization<Enemy>>(str).ToList();

결과)
str의 출력 결과는 다음과 같다.
{"target":[{"name":"오거","skills":["물리공격","마법"]},{"name":"트롤","skills":[" 공격","회복","부활"]}]}
retEnemies에 값을 넣는다.

3. Dictionary 직렬화

[Serializable]
public class Serialization<TKey, TValue> : ISerializationCallbackReceiver
{
    [SerializeField]
    List<TKey> keys;
    [SerializeField]
    List<TValue> values;

    Dictionary<TKey, TValue> target;
    public Dictionary<TKey, TValue> ToDictionary() { return target; }

    public Serialization(Dictionary<TKey, TValue> target)
    {
        this.target = target;
    }

    public void OnBeforeSerialize()
    {
        keys = new List<TKey>(target.Keys);
        values = new List<TValue>(target.Values);
    }

    public void OnAfterDeserialize()
    {
        var count = Math.Min(keys.Count, values.Count);
        target = new Dictionary<TKey, TValue>(count);
        for (var i = 0; i < count; ++i)
        {
            target.Add(keys[i], values[i]);
        }
    }
}

//Test
var enemies = new Dictionary<int, Enemy>();
enemies.Add(1000, new Enemy("오거", new List<string>() { "물리공격", "마법" }));
enemies.Add(1001, new Enemy("트롤", new List<string>() { "공격", "회복", "부활" }));

string str = JsonUtility.ToJson(new Serialization<int, Enemy>(enemies));
Debug.Log(str);

Dictionary<int, Enemy> retEemies = JsonUtility.FromJson<Serialization<int, Enemy>>(str).ToDictionary();

결과)
str의 출력 결과는 다음과 같다.
{"keys":[1000,1001],"values":[{"name":"오거","skills":["물리공격","마법"]}, {"name":"트롤","skills":["공격","회복","부활"]}]}

ISerializationCallbackReceiver로 직렬화 기능확장

Unity 5.3에서 "UnityEngine.ISerializationCallbackReceiver"가 추가 되었다.
ISerializationCallbackReceiver를 상속하면 직렬화(ToJson)전에 OnBeforeSerialize가 호출되고
역직렬화(FromJson)전에 OnAfterDeserialize가 호출된다.

public  class Character : ISerializationCallbackReceiver
{
    public  void OnBeforeSerialize ()
    {
        Debug.Log ( "OnBeforeSerialize" );
    }

    public  void OnAfterDeserialize ()
    {
        Debug.Log ( "OnAfterDeserialize" );
    }
}

참조)
http://kou-yeung.hatenablog.com/entry/2015/12/31/014611
https://docs.unity3d.com/ScriptReference/JsonUtility.html
https://docs.unity3d.com/ScriptReference/ISerializationCallbackReceiver.html





Unity3D - JsonUtility 사용법 익히기

유니티 5.3버전부터 JSON 포맷의 데이터를 파싱할 수 있는 API가 추가되었다. 플러그인을 추가하지않고, 내장된 API로만 구현을 하는 게 좋겠다싶어 조금 알아봤지만 만족스럽진 않았다.
일단 예제를 만들어서 테스트해보자.


준비1

Serialize할 수 있는 간단한 데이터 포맷 클래스를 만든다.
[Serializable]
public class PlayerInfo
{
    public string name;
    public int lives;
    public float strength;
}


준비2

JsonHelper 클래스를 따로 만들어준다. JsonUtility의 가장 큰 단점이 배열을 받을 수 없다는 것인데 다음과 같이 JsonObject를 감싸는(JsonObject 배열을 가지는) Wrapper 클래스를 따로 만들어서 문제를 해결할 수 있다.
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;
    }
}


테스트 코드

using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections.Generic;

public class JsonTestWindow : EditorWindow {

    [MenuItem("JSON/Test 1")]
    static void Init()
    {
        JsonTestWindow window = (JsonTestWindow)EditorWindow.GetWindow(typeof(JsonTestWindow));
        window.Show();
    }

    void OnGUI()
    {
        if (GUILayout.Button ("Save")) {
            Save ();
        }
        if (GUILayout.Button ("Load")) {
            Load ();
        }
    }

    void Save()
    {
        //...아래에서
    }

    void Load()
    {
        //...//아래에서
    }
}


Save()

void Save()
{
  PlayerInfo[] playerInfo = new PlayerInfo[2];

  playerInfo[0] = new PlayerInfo();
  playerInfo[0].name = "12341234";
  playerInfo[0].lives = 5;
  playerInfo[0].strength = 25.0f;

  playerInfo[1] = new PlayerInfo();
  playerInfo[1].name = "1235235";
  playerInfo[1].lives = 3;
  playerInfo[1].strength = 30.2f;

  string toJson = JsonHelper.ToJson(playerInfo, prettyPrint:true);
  File.WriteAllText (Application.dataPath + "/Saves/data.json", toJson);
}


Load()

void Load()
{
  string jsonString = File.ReadAllText (Application.dataPath + "/Saves/data.json");
  var data = JsonHelper.FromJson <PlayerInfo>(jsonString);

  foreach (var playerInfo in data) {
    Debug.Log (playerInfo.name);
    Debug.Log (playerInfo.lives);
    Debug.Log (playerInfo.strength);
  }
}
편의를 위해서 EditorWindow로 테스트 해보았다.
별다른 플러그인 없이 쉽게 구현이 가능하다는 장점이 있지만, 인덱싱이 안되고 데이터포맷을 미리 알지못하면 처리할 수 없다는 문제가 있으므로 간단한 데이터 저장용 외에는 활용하기 어려울 것 같다.


[JsonUtility에서 List <T>와 Dictionary <TKey, TValue> 직렬화]

Unity5.3에서 JsonUtility 추가되었다. 
하지만, List와 Dictionary는 직렬화 할 수 없습니다!
직렬화 대상이되기 위해서는 다음의 조건이있다
1. 클래스에 [Serializable] 속성
2.private 멤버 변수에 [SerializeField]의 특성
3.public 멤버 변수로
물론, List와 Dictionary은 이상의 조건에 부합하지 않는
예를 들어 다음 클래스를 사용합니다
using UnityEngine;
 using System;
 using System.Collections.Generic;

[Serializable]
public  class Enemy
{
    [SerializeField]
    string name;
    [SerializeField]
    List < string > skills;

    public Enemy ( string name List < string > skills)
    {
        this .name = name;
         this .skills = skills;
    }
}
실제로 연재 해 본다
var enemies = new List <Enemy> ();
enemies.Add ( new Enemy ( "슬라임" , new List < string > () { "공격" }));
enemies.Add ( new Enemy ( "킹 슬라임" , new List < string > () { "공격" , "회복" }));
Debug.Log (JsonUtility.ToJson (enemies));
// 출력 : {}
아무것도 Json 문자열로 출력되었다.
Unity의 공식 사이트에서는 ISerializationCallbackReceiver 을 상속하는 방법을 제시했지만, 
좀 더 추상화하고 싶기 때문에 ...
써 보았다
// Serialization.cs 
using UnityEngine;
 using System.Collections;
 using System.Collections.Generic;
 using System;

// List <T>
[Serializable]
public  class Serialization <T>
{
    [SerializeField]
    List <T> target;
    public List <T> ToList () { return target;}

    public Serialization (List <T> target)
    {
        this .target = target;
    }
}

// Dictionary <TKey, TValue>
[Serializable]
public  class Serialization <TKey, TValue> : ISerializationCallbackReceiver
{
    [SerializeField]
    List <TKey> keys;
    [SerializeField]
    List <TValue> values;

    Dictionary <TKey, TValue> target;
    public Dictionary <TKey, TValue> ToDictionary () { return target;}

    public Serialization (Dictionary <TKey, TValue> target)
    {
        this .target = target;
    }

    public  void OnBeforeSerialize ()
    {
        keys = new List <TKey> (target.Keys);
        values = new List <TValue> (target.Values);
    }

    public  void OnAfterDeserialize ()
    {
        var count = Math.Min (keys.Count, values.Count);
        target = new Dictionary <TKey, TValue> (count);
         for (var i = 0 ; i <count; ++ i)
        {
            target.Add (keys [i], values ​​[i]);
        }
    }
}
사용법
// List <T> -> Json 문자열 (예 : List <Enemy>) 
string str = JsonUtility.ToJson ( new Serialization <Enemy> (enemies)); // 출력 : { "target": [{ "name ":"슬라임 ","skills ":"공격 "]}, {"name ":"킹 슬라임 ","skills ":"공격 ","회복 "]}]} 
// Json 문자열 -> List <T>
List <Enemy> enemies = JsonUtility.FromJson <Serialization <Enemy >> (str) .ToList ();

// Dictionary <TKey, TValue> -> Json 문자열 (예 : Dictionary <int, Enemy>) 
string str = JsonUtility.ToJson ( new Serialization < int , Enemy> (enemies)); // 출력 : { "keys ": [1000,2000]"values ": [{"name ":"슬라임 ","skills ":"공격 "]}, {"name ":"킹 슬라임 ","skills ":"공격 ","회복 "]}]} 
// Json 문자열 -> Dictionary <TKey, TValue> 
Dictionary < int , Enemy> enemies = JsonUtility.FromJson <Serialization < int , Enemy >> (str) .ToDictionary ();
게다가 BitArray도 써보기로했다
// BitArray
[Serializable]
public  class SerializationBitArray : ISerializationCallbackReceiver
{
    [SerializeField]
    string flags;

    BitArray target;
    public BitArray ToBitArray () { return target;}

    public SerializationBitArray (BitArray target)
    {
        this .target = target;
    }

    public  void OnBeforeSerialize ()
    {
        var ss = new System.Text.StringBuilder (target.Length);
         for (var i = 0 ; i <target.Length; ++ i)
        {
            ss.Insert ( 0 , target [i]? '1' : '0' );
        }
        flags = ss.ToString ();
    }

    public  void OnAfterDeserialize ()
    {
        target = new BitArray (flags.Length);
         for (var i = 0 ; i <flags.Length; ++ i)
        {
            target.Set (flags.Length - i - 1 , flags [i] == '1' );
        }
    }
}
사용법
BitArray bits = new BitArray ( 4 );
bits.Set ( 1 , true );

// BitArray -> Json 문자열 
var str = JsonUtility.ToJson ( new SerializationBitArray (bits)); // 출력 : { "flags": "0010"} 
// Json 문자열 -> BitArray
BitArray bits = JsonUtility.FromJson <SerializationBitArray> (s) .ToBitArray ();

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
[Serializable]
public class Serialization<T>
{
[SerializeField]
List<T> target;
public List<T> ToList() { return target; }
public Serialization(List<T> target)
{
this.target = target;
}
}
[Serializable]
public class Serialization<TKey, TValue> : ISerializationCallbackReceiver
{
[SerializeField]
List<TKey> keys;
[SerializeField]
List<TValue> values;
Dictionary<TKey, TValue> target;
public Dictionary<TKey, TValue> ToDictionary() { return target; }
public Serialization(Dictionary<TKey, TValue> target)
{
this.target = target;
}
public void OnBeforeSerialize()
{
keys = new List<TKey>(target.Keys);
values = new List<TValue>(target.Values);
}
public void OnAfterDeserialize()
{
var count = Math.Min(keys.Count, values.Count);
target = new Dictionary<TKey, TValue>(count);
for (var i = 0; i < count; ++i)
{
target.Add(keys[i], values[i]);
}
}
}
[Serializable]
public class SerializationBitArray : ISerializationCallbackReceiver
{
[SerializeField]
string flags;
BitArray target;
public BitArray ToBitArray() { return target; }
public SerializationBitArray(BitArray target)
{
this.target = target;
}
public void OnBeforeSerialize()
{
var ss = new System.Text.StringBuilder(target.Length);
for(var i = 0 ; i < target.Length ; ++i)
{
ss.Insert(0, target[i]?'1':'0');
}
flags = ss.ToString();
}
public void OnAfterDeserialize()
{
target = new BitArray(flags.Length);
for (var i = 0; i < flags.Length; ++i)
{
target.Set(flags.Length - i - 1, flags[i] == '1');
}
}
}




댓글 3개:

  1. public 을 사용하지않고 SerializeField를 사용했다는 말은 무슨 뜻입니까?

    답글삭제
    답글
    1. private 속성을 유니티 인스펙터에서 사용할 때 사용합니다.

      삭제
  2. 알쓸다잡 (알아두면 쓸데있는 다양한 잡식): Unity - Local Db - C - Jsonutility >>>>> Download Now

    >>>>> Download Full

    알쓸다잡 (알아두면 쓸데있는 다양한 잡식): Unity - Local Db - C - Jsonutility >>>>> Download LINK

    >>>>> Download Now

    알쓸다잡 (알아두면 쓸데있는 다양한 잡식): Unity - Local Db - C - Jsonutility >>>>> Download Full

    >>>>> Download LINK

    답글삭제

javascript - SQL 예약어 제거

  <script language="javascript"> //특수문자, 특정문자열(sql예약어) 제거 function checkSearchedWord(obj){ obj.value = obj.value+&quo...