해당 포스트는 "안드로이드 통신+보안 프로그래밍" 책의 내용을 요약한 것이다.



※ 제이슨

: 플랫폼이나 언어와 무관하게 자료 교환을 목적으로 만들어진 포맷이다. 제이슨은 완전성과 가독성이 좋고 표현이 간단하다. 크기도 작아 데이터 전달 속도가 빠르다. 따라서 서버가 메시지를 전달할 때 XML보다 JSON을 더 선호한다. 제이슨은 자바 콜렉션 맵(Map) 처럼 키와 값의 쌍으로 구성되 언제든지 제이슨을 맵으로 전환 가능하다. 제이슨의 문법은 다음과 같다.

- 일반 문자로 이루어지고 문자셋은 유니코드이다.

- MIME type은 'application/json'이다.

- "{"로 시작하고 "}"로 끝난다.

- ","는 제이슨을 구분하는 데 사용한다.

- ":"는 키와 값을 구분한다.

- 제이슨 키와 문자열 타입 값은 큰따옴표로 묶는다.

- 제이슨 객체는 이름과 값을 갖는 순서가 없는 집합이다.

- 제이슨 배열은 "[","]"로 묶으며 순서를 가진다.

- 만약 이미지를 제이슨으로 만들어 전송하려면 Base64로 이미지를 문자열로 변형하여 전송해야 한다.

byte[] encoded = Base64.encodeBase64(hello.getBytes());
String encodedString = new String(encoded);
ex)제이슨 예
{
 "menu" : {
    "id" : "file",
    "value" : "File",
     "popup" : {
        "menuitem" : [
         {"value":"New","onclick":"CreateNewDoc()"},
         {"value":"Open","onclick":"OpenDoc()"},
                                  {"value":"Close","onclick":"CloseDoc()"}
                                   ]
                     }
        }
}

위 예를 보자면 "menu" 키는 "id","value","popup" 키를 가지는 제이슨들을 값으로 가진다. "popup"을 키로 가지는 제이슨은 "menuitem"을 키로 가지는 제이슨 배열을 가진다. 여기서 "menu"는 1레벨이다. "id", "value", "popup"은 2레벨이고 "menuitem"은 3레벨이다.

제이슨은 제이슨 객체 생성자에 의해서 제이슨 객체로 만들 수 있다.

- public JSONObject()

- public JSONObject(Map copyFrom)

- public JSONObject(JSONTokener readFrom)

- public JSONObject(String json)

다음은 문자열로 제이슨 객체를 생성하는 예이다.

String json="{"+"\"query\":\"Pizza\","+"\"locations\":50000"+"}"
JSONObject object = new JSONObject(json);     

제이슨과 자바의 문자열이 서로 다른 구조를 가지고 있다. 따라서 제이슨의 경우 키와 문자열 타입에 "가 들어가야 하는데 자바에서 큰따옴표를 넣으려면 '\'문자를 사용해야 한다. 그래서 복잡하다. 또한 위 String json 변수의 형식이 JSON 문법에 맞지 않으면 제이슨 객체를 생성할 때 예외가 발생한다.

ex) 서버에서 다운로드 받은 제이슨 문서 읽기(안드로이드에서 사용 가능)

{"earthquakes":[
             {"eqid":"2010swaf",
              "magnitude":6.9,
              "src":"us",
              "depth":573.8
             },
                 ......
           ]
}

String result = .....//서버에서 다운받은 JSON 문서
try{
     //다운로드한 제이슨 문서를 제이슨 객체로 만든다
     JSONObject json = new JSONObject(result);
     //제이슨에서 earthquakes는 제이슨 배열이다.
     JSONArray earthquakes = json.getJSONArray("earthquakes");

 

for(int i=0; i<earthquakes.Length();i++){
  HashMap<String,String> map = new HashMap<String,String>();
  
  //제이슨 객체를 하나씩 읽어 맵으로 전환한다.
  JSONObject e = earthquakes.getJSONObject(i);
  map.put("id", String.valueOf(i));
  map.put("name", e.getString("eqid"));
  map.put("magnitude", e.getString("magnitude"));
  map.put("src", e.getString("src"));
  map.put("depth", e.getDouble("depth"));
  mapList.add(map);
 }
}catch(JSONException e){}

제이슨은 위와 같이 맵으로 빠르게 전환이 가능하다. 자바에서는 각 객체 타입 별로 getXXXX 메서드와 optXXXX메서드를 제공한다. get메서드는 매개변수에 해당하는 제이슨 객체가 존재하지 않으면 JSONException 예외를 발생시킨다. opt 메서드는 그 때 디폴트값을 반환한다. 만약 optXXXX(key) 메서드를 호출했을 때 제이슨 객체가 존재하지 않으면 예외를 발생시키지 않고 null을 반환한다. 또한 optBoolean(key)의 경우 제이슨 객체가 존재하지 않으면 "false"를 반환한다. 따라서 주의해야 한다. 추가적으로 제이슨 관련 메서드를 살펴보자.

- boolean has(String name) : name 키를 가지는 제이슨 객체가 있는지 확인한다.

- boolean isNull(String name) : name 키를 가지는 제이슨 객체가 null 값을 가지는 지 확인한다.

- java.util.Iterator keys() : 제이슨 객체의 키를 사용하여 Iterator 객체를 반환한다. 제이슨 배열을 Iterator 객체로 만들 때 사용한다.

- int length() : 제이슨 객체가 가지는 키의 수를 반환한다.

- JSONObject put(String name, 타입 value) : name키와 value 값을 입력해 제이슨 객체를 만들고 반환한다. 만약 null 값을 입력하고자 하면 JSONObject.NULL 상수를 입력해야 한다.

- static String quot(String string) : string 문자열을 제이슨 키와 값으로 사용할 수 있도록 백슬래시 따옴표를 붙여준다.

- Object remove(String name) : name 키에 해당하는 제이슨 객체를 삭제한다.

- String toString() : 제이슨 객체를 문자열로 변환한다.

- JSONObject accumulate(String name, Object value) : name이라는 키에 value 값을 추가 또는 수정하여 제이슨 객체를 만든다. 만약 name으로 선언된 제이슨 객체가 없거나 제이슨 배열이라면 추가한다. name으로 선언된 제이슨 객체가 있고 제이슨 배열이 아니라면 수정한다. 

추가적으로 위 예는 1,2레벨로만 이루어져 있다. 만약에 3레벨까지 있다면 1레벨에서 2레벨 제이슨 객체에 접근하고 2레벨에서 3레벨 제이슨 객체에 접근해야 한다. 그렇지 않으면 예외가 발생한다. 이 때 만약 opt 메서드를 사용하면 예외를 발생시키지 않고 디폴트 값을 반환하기 때문에 더욱 주의해야 한다.

 

ex) 맵을 제이슨으로 전환 후 서버 전송(안드로이드에서 사용 가능)

//맵을 생성한다.
DataObject obj = new DataObject();
ouputStream.write(obj.toString().getBytes());
outputStream.close();

DataObject 클래스는 밑에서 볼 예정이다. 먼저 obj라는 맵을 만든다. obj.toString을 호출해 제이슨 형식에 맞춰서 String 객체를 만든 후 전송하면 된다. 전송할 JSON은 해당 포스트 제일 위에서 봤던 1레벨이 "menu"인 JSON이다.
 

import org.json.JSONObject;

public class DataObject {
     private final Map<String, Object> menu;
  
     public DataObject() {
          List<Map<String, String>>menuitem = new ArrayList<Map<String, String>>();
          Map<String, String> map01 = new HashMap<String, String>();
          map01.put("value", "New");
          map01.put("onclick", "CreateNewDoc()");
          menuitem.add(map01);
          Map<String, String> map02 = new HashMap<String, String>();
          map02.put("value", "Open");
          map02.put("onclick", "OpenDoc()");
          menuitem.add(map02);
          Map<String, String> map03 = new HashMap<String, String>();
          map03.put("value", "Close");
          map03.put("onclick", "CloseDoc()");
          menuitem.add(map03);
      
          menu = new HashMap<String, Object>();
          menu.put("id", "file");
          menu.put("value", "File");
          menu.put("popup", menuitem);
     }
 
     @Override
     public String toString() {
          StringBuffer sb = new StringBuffer("{\"menu\":{");
  
          sb.append(JSONObject.quote("id") + ":");
          sb.append(JSONObject.quote((String) menu.get("id")) +",\n");
          sb.append("\"value\":");
          sb.append(JSONObject.quote((String) menu.get("value")) +",\n");
          sb.append("\"popup\": {\"menuitem\":[");
          List<Map<String, String>> menuitem = (List) menu.get("popup");
    
          for (int i = 0; i < menuitem.size(); i++) {
               if (i > 0) sb.append(",");
               Map<String, String> map = menuitem.get(i);
               sb.append(new JSONObject(map).toString());
          }
          sb.append("]}\n}}"); 
             return sb.toString(); 
    }
}

toString() 내부를 구현하는 데 JSONStringer 클래스를 사용하면 다른 방식으로 구현이 가능하다. 다음은 JSONStringer에서 제공하는 메서드이다.

- object() : '{'

- endObject() : '}'

- arrays() : '['

- endArrays() : ']'

- key() : 큰 따옴표로 덮인 키와 ":", 제이슨 객체 뒤에 사용하면 ','도 자동으로 붙는다.

- value(double value), value(boolean value), value(Object value) : 매개변수 형이 Object인 메서드는 제이슨 객체, 제이슨 배열, 문자열, 정수(Integer), 실수(Long)이 들어갈 수 있다.

public String toString(){
     JSONStringer ja = new JSONStringer();
     try{
          ja.object();
          ja.key("menu");
          ja.object();
          ja.key("id").value("file").key("value").value("File");
          ja.key("popup");
          ja.object();
          ja.key("menuitem");
          ja.array();
          for(int i=0; i<menuitem.size();i++)
          {
               Map<String,String> map = menuitem.get(i);
               ja.object();
               ja.key("value").value(map.get("value"));
               ja.key("onclick").value(map.get("onclick));
               ja.endObject();
          }
          ja.endArray();
          ja.endObject();
          ja.endObject();
          ja.endObject();
         }catch(JSONException e){}
     return ja.toString();
}
위 예제에서 for문을 일반화해서 다음과 같이 바꿀 수 있다.

for(int i=0; i<menuitem.size();i++)
     Map<String,String> map = menuitem.get(i);
     ja.object();
     Iterator<Entry<String,String>> it = map.entrySet().iterator();
     while(it.hasNext()){
          Map.Entry<String,String>> pairs = it.next();
          ja.key(pairs.getKey()).value(pairs.getValue());
     }
     ja.endObject();
}

+ Recent posts