#Chromium #JSON

다른 포스트에서도 언급했거나 언급하겠지만, 크로미움은 그 자체로 매우 풍부한 라이브러리 집합이다. 예를 들면, Thread, GURL 클래스를 이용하면 손쉽게 스레드를 만들고 관리할 수 있으며, URL도 입맛대로 다룰 수 있다. (scheme만 따로 추출하려고 굳이 파싱하지 않아도 되는) 이 밖에도 유용한 클래스들이 많이 존재하며, 이들은 크로미움의 base 네임스페이스에 존재한다.

크로미움은 웹 브라우저이므로 당연히 JSON(JavaScript Object Notation)을 처리하기 위한 모듈이 존재할 것이다. 아니, 존재하여야만 한다. 실제로, base 네임스페이스 아래에 JSONReader 클래스가 존재한다. 물론, libjson-clibjsoncpp와 같은 라이브러리를 활용하는 옵션도 있겠지만, 크로미움 개발자라면 굳이 이를 활용할 필요가 없겠다. 본 포스트에는 크로미움 내에서 JSON을 다루는 방법을 담았다.

1. Open and read file

처리하고자 하는 대상은 파일 형태로 존재할 수도 있고, 네트워크로 받아와 바이트 스트림 형태로 존재할 수도 있다. 파일 형태도 결국은 처리를 위해서 바이트 스트림 형태로 변환해야 하므로 전자의 경우를 보겠다.

크로미움은 심지어 파일 경로를 처리하는 클래스까지 갖고 있다. 해당 클래스는 FilePath이다. 굳이 파일 경로를 처리하는 클래스가 필요한 지 의문이 들 수 있지만, 플랫폼마다 파일 경로를 구성하는 규칙이 다르기 때문에라도 매우 유용하고 중요한, 생각보다 복잡한 클래스이다. 처리하고자 하는 파일이 test.json이고, 파일 경로가 “/path/to/file/test.json"이라면 다음과 같이 FilePath 객체를 생성할 수 있다.

base::FilePath json_path("/path/to/file/test.json");

위 코드로 json_path라는 FilePath 객체가 생성된다. 이제 이 객체를 이용하여 test.json을 읽고, 그 내용을 바이트 스트림 형태로 가져와야 한다. 이 작업은 ReadFileToString() 함수를 이용하여 아래와 같이 수행할 수 있다.

std::string json_string;
base::ReadFileToString(json_path, &json_string);

위 코드로 test.json의 내용이 문자열 객체인 json_string에 저장된다.

2. Parse JSON and Ready to play with it

json_string은 test.json의 내용을 바이트 스트림으로 저장하고 있을 뿐인 문자열 객체에 불과하다. 이 문자열을 JSON 문법에 맞추어 취급하기 쉬운 형태로 파싱하여야 한다. 생각만해도 번거로운 이 작업은 base 네임스페이스 내 JSONReader 클래스의 멤버 함수인 Read()로 쉽게 처리할 수 있다. static 함수이므로 다음과 같이 활용할 수 있다.

std::unique_ptr<base::Value> json_structure;
json_structure = base::JSONReader::Read(json_str);

위 코드로 json_structure라는 Value 객체가 생성된다. 이를 이용하여 test.json 내의 모든 키 값들을 추출할 수 있다.

3. Getting a dictionary from a JSON

JSON은 다음의 두 개의 구조를 바탕으로 구성된다. JSON 공식 홈페이지에 두 구조에 대한 간략한 설명이 존재한다.

  • Collection
    • name/value pairs. In various languages this is realized as an object, record, struct, dictionary, hash table, keyed list, or associative array.
  • Ordered list of values
    • realized as an array, vector, list, or sequence.

JSON은 CollectionOrdered list로 구성된다. 따라서, JSON으로부터 이 두 구조를 얻어낼 수 있다는 것은 곧, JSON을 완벽하게 활용할 수 있다는 것이다. CollectionOrdered list를 구현하는 방법은 언어에 따라 다르다. 크로미움에서는 각각 DictionaryList로 표현한다. 그리고, base 네임스페이스에는 Value 클래스를 상속받는 DictionaryValueListValue라는 클래스가 존재한다. 뭔가 감이 오기 시작한다. 더 진행하기에 앞서서, test.json의 내용이 다음과 같다고 가정하자.

{
   "key1" : {
      "key11" : "value11",
      "key12" : [ "element121",
                  "element122"
                ]
}

JSON 전쳬가 하나의 Collection, 즉, Dictionary이다. 따라서 앞서 파싱하여 얻어낸 Value 객체인 json_structure로부터 DictionaryValue 객체를 얻어내야 한다. 그래야만 key11의 값인 “value11"과 Ordered list인 key12가 포함하는 값들인 “element121”, “element122"를 추출할 수 있을 것이다. DictionaryValue 객체를 얻어내기 위해서는 DictionaryValue 클래스의 From() 함수를 이용하면 된다. static 함수이므로 다음과 같이 활용할 수 있다.

std::unique_ptr<base::DictionaryValue> json_dictionary;
json_dictionary = base::DictionaryValue::From(json_structure);

위 코드로 json_dictionary라는 DictionaryValue 객체를 얻어낼 수 있다.

4. Getting a value which type is “string” from a dictionary

크로미움에서만 그런지는 모르겠지만 “.”을 이용하여 특정 키의 경로를 표현할 수 있다. 예를 들어, Key11의 주소는 “key1.key11"이다. 이 경로를 이용하여 해당 키의 값을 추출할 수 있다. key11에 대응되는 값의 타입은 문자열이므로 DictionaryValue 클래스의 GetString() 함수를 아래와 같이 활용할 수 있다.

std::string key11_value;
json_dictionary->GetString("key1.key11", &key11_value);

5. Getting a list from a dictionary

key12는 대괄호로 둘러쌓인 Ordered list, 즉, List이다. 따라서 이번에는 DictionaryValue 객체로부터 std::string이 아닌, ListValue객체를 추출해야 한다. 이를 위해 DictionaryValue 클래스의 GetList() 함수를 아래와 같이 활용할 수 있다.

base::ListValue* key12_list;
json_dictionary->GetList("key1.key12", &key12_list);

6. Iterate and getting a value which type is “string” from a list

앞서 얻어낸 ListValue 객체인 key12_list의 원소들을 순회하여 Ordered list인 key12의 모든 원소들을 추출할 수 있다. DictionaryValue, ListValue 클래스는 Value 클래스를 상속받는다. ListValue의 경우에는 원소들이 부모 클래스인 Value가 정의하는 멤버 변수, ListStorage에 저장된다. 아래와 같은 연쇄 대입 체인에 따르면,

// base/values.h
// https://cs.chromium.org/chromium/src/base/values.h?rcl=029daddc376494ee36c5d81cc51a5ade45002fb6&l=589
class ListValue : public Value {
using const_iterator = ListStorage::const_iterator;

}
// base/values.h
// https://cs.chromium.org/chromium/src/base/values.h?rcl=029daddc376494ee36c5d81cc51a5ade45002fb6&l=85
ListStorage = std::vector<Value>;

ListStorage는 결국 std::vector 객체이다. 즉, std::vector 객체를 순회하는 방법을 그대로 활용하여 원소들을 순회할 수 있다.

ListValue::const_iterator == std::vector<Value>::const_iterator

순회 중에 방문한 원소들의 타입은 무엇일까? 타입을 알아야 값을 추출할 수 있다. 위 등식에서도 알 수 있듯이, iterator 또는 const_iterator에 의해 참조하는 각 원소들은 base::Value* 또는 const base::Value*이다. 한편, Value 객체의 값은 멤버 함수인 GetString()으로 얻어낼 수 있다.(키 값의 타입이 문자열인 경우라면) 아래가 그 예제다.

for(base::ListValue::const_iterator it = key12_list->begin(); 
     it != key12_list->end(); ++it) {
   std::string key12_string() it->GetString();
}

Full example

test.json으로부터 모든 값을 추출하는 전체 예제 코드는 다음과 같다.

base::FilePath json_path("/path/to/file/test.json");
std::string json_string;
std::unique_ptr<base::Value> json_structure;
std::unique_ptr<base::DictionaryValue> json_dictionary;
std::string key11_value;
base::ListValue* key12_list;

base::ReadFileToString(json_path, &json_string);
json_structure = base::JSONReader::Read(json_str);
json_dictionary = base::DictionaryValue::From(json_structure);
json_dictionary->GetString("key1.key11", &key11_value);
json_dictionary->GetList("key1.key12", &key12_list);
for(base::ListValue::const_iterator it = key12_list->begin(); 
     it != key12_list->end(); ++it) {
   std::string key12_string() it->GetString();
}