본문 바로가기

JAVA/Android

[JAVA][Android] JSON / GSON / JACKSON

데이터 작업 시 자주 사용하는 JSON과 GSON, Jackson를 분석하여 정리해보겠다.

JSON
 : 자바스크립트에서 사용하는 객체 표기법

https://developer.android.com/reference/org/json/package-summary?hl=en 

 

org.json  |  Android Developers

android.net.wifi.hotspot2.omadm

developer.android.com

JSON은 네 종류의 Class, 한 종류의 Exception를 가지고 있다.
체감상 자주 사용하는 Class는 JSONObject와 JSONArray이며
주로 서버와 웹 또는 앱 간의 데이터 통신 시 사용하는 데이터 형식이다.
Key와 Value의 쌍으로 이루어진 형태이다.

*서버에서 받는 JSON 데이터를 파싱하는 법

 

우선 assets 폴더에 json 파일을 작성한다.

new -> File을 선택하면 된다.

 

assets/jsonData.json 을 작성했다.

{
  "fruit": [
    {
      "name": "apple",
      "color": [
        "red",
        "green",
        "yellow"
      ]
    },
    {
      "name": "banana",
      "color": [
        "yellow",
        "green"
      ]
    }
  ]
}

 

JSONPractice.java 파일을 생성했다.

 

 - assets 폴더에 작성한 json 파일을 스트림으로 읽어와 String 객체에 저장한다.

 - new JSONObject 를 통해 해당 String 객체를 JSONObject 객체로 변환한다.

 - JSONObject 객체를 Parser 클래스로 보낸다.

import android.os.Bundle;
import android.util.Log;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

public class JSONPractice extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String json = "";

        try {
            InputStream is = null;

            is = getAssets().open("jsonData.json");

            int fileSize = is.available();

            byte[] buffer = new byte[fileSize];
            is.read(buffer);
            is.close();

            json = new String(buffer, "UTF-8");
            Log.d("json practice/ json text : ",json);

            JSONPracticeParser parser = new JSONPracticeParser();

            JSONObject jsonObject = new JSONObject(json);
            parser.parseData(new FRUITData(),jsonObject);

        } catch (IOException | JSONException e) {
            e.printStackTrace();
        }
    }
}

JSONPracticeParser 파일을 생성했다.

해당 위치에서 JSONObject 객체를 읽어와 필드 별로 파싱할 것이다.

 

 - 우선 json 데이터가 배열 형태이기 때문에

   전달받은 JSONObject 객체에서 getJSONArray를 통해 array 형태로 추출한다.

 - 파싱한 array를 저장할 ArrayList 또한 생성한다.

 - 반복문을 통해 ArrayList에서 JSONObject를 차례로 읽어와 데이터 클래스에 저장한다.

   (**JSONObject 안에 JSONObject가 있는 셈이다.

    큰 JSONObject에서 JSONArray 추출 => JSONArray에서 여러개의 작은 JSONObject 추출**)

 - 완성된 데이터 클래스의 객체는 ArrayList에 저장해둔다.

 - 마지막으로 데이터 클래스에 완성된 ArrayList를 set 해주면 된다.

import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.ArrayList;

public class JSONPracticeParser {
    public void parseData(FRUITData fruitData, JSONObject jsonObject) {
        if(fruitData == null || jsonObject == null) {
            return;
        }

        try {

            JSONArray jsonArray = jsonObject.getJSONArray("fruit");

            ArrayList<FRUITData.FruitItem> fruitItemArrayList = new ArrayList<FRUITData.FruitItem>();

            for(int i=0; i<jsonArray.length(); i++){
                FRUITData.FruitItem fruitItem = fruitData.getFruitItemInstance();

                JSONObject object = jsonArray.getJSONObject(i);

                if (!object.isNull("name")) {
                    String name = object.getString("name");
                    Log.d("json practice/ name : ",name);
                    fruitItem.setName(name);
                }

                if (!object.isNull("color")) {
                    String color = object.getString("color");
                    Log.d("json practice/ color : ",color);
                    fruitItem.setColor(color);
                }

                fruitItemArrayList.add(fruitItem);
            }

            fruitData.setFruitList(fruitItemArrayList);

        }
        catch (JSONException e) {
            e.printStackTrace();
        }
    }
}

필요한 데이터 클래스이다.

import java.util.ArrayList;

public class FRUITData {

    private ArrayList<FruitItem> fruitList = new ArrayList<FruitItem>();

    public ArrayList<FruitItem> getFruitList() {
        return fruitList;
    }

    public void setFruitList(ArrayList<FruitItem> fruitList) {
        this.fruitList = fruitList;
    }

    public class FruitItem{
        private String name = null;
        private String color = null;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getColor() {
            return color;
        }

        public void setColor(String color) {
            this.color = color;
        }
    }

    public FruitItem getFruitItemInstance(){
        FruitItem fruitItem = new FruitItem();
        return fruitItem;
    }

}

처음에 File로 작성한 json의 내용과 그 후 파싱한 데이터가 제대로 남겨지는 걸 확인할 수 있다.

이렇게 JSON 데이터를 활용하는 예시를 들어봤다.

GSON
 : 구글에서 제공하는 JSON 라이브러리로, 주로 JSON 데이터를 JAVA 객체로 변환할 때 주로 사용한다.

자주 사용하는 메소드로는 fromJson, toJson이 있다.

 

#1 fromJson을 이용하여 JSONObject 객체를 JAVA 객체로 변환하기

우선 build.gradle의 dependencies에 추가해주어야 한다.

dependencies {
    implementation 'com.google.code.gson:gson:2.8.5'
}

위와는 다르게 단일 형태의 json 파일을 생성하였다.

jsonDataNotArray.json

{
    "name": "apple",
    "color": [
      "red",
      "green",
      "yellow"
    ]
}

액티비티에서는 첫번째 예시와 다르게 json 데이터를 String 형식으로 전달하였다.

is = getAssets().open("jsonDataNotArray.json");

int fileSize = is.available();

byte[] buffer = new byte[fileSize];
is.read(buffer);
is.close();

json = new String(buffer, "UTF-8");
Log.d("json practice/ json text : ",json);

JSONPracticeParser parser = new JSONPracticeParser();

parser.parseDataFromGSON(json);

Parser 클래스에도 GSON을 활용하는 parse 메소드를 따로 작성했다.

public void parseDataFromGSON(String json){
    Gson gson = new Gson();

    FRUITData.FruitItemGSON fruitItem = gson.fromJson(json,FRUITData.FruitItemGSON.class);

    Log.d("json practice/ name : ",fruitItem.getName());
    for(int i=0; i<fruitItem.getColor().length; i++)
        Log.d("json practice/ color : ",fruitItem.getColor()[i]);
}

여기서 주의할 점은 데이터 클래스를 수정해야 한다는 것이다.
문제가 되는 것은 color필드인데, 기존의 jsonObject.getString("color")를 했던 것과 달리
GSON을 활용하여 데이터를 추출할 경우에는 데이터 타입을 따로 정의할 수 없기 때문에 
그대로 사용하면 아래와 같은 문제가 발생한다.

json 파일에 color필드는 사실 String[] 형태로 작성되어 있어서,
데이터 클래스에도 String[]으로 선언해두어야 이런 이슈를 피할 수 있다.

public class FruitItemGSON{
    private String name = null;
    private String[] color = null;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String[] getColor() {
        return color;
    }

    public void setColor(String[] color) {
        this.color = color;
    }
}


핵심 : GSON을 활용하여 데이터 작업을 할 때에는 JSON파일 내용과 데이터 형식이 꼭 일치해야 한다.

 

color 필드는 반복문을 활용하여 String 리스트의 값을 하나씩 출력한 것이다.

 

#2 toJson을 이용하여 JAVA 객체를 JSONObject 객체로 변환하기

 

기존 메소드에 gson.toJson을 이용해 한 줄만 추가로 작성하였다.

public void parseDataFromGSON(String json){
    Gson gson = new Gson();

    FRUITData.FruitItemGSON fruitItem = gson.fromJson(json,FRUITData.FruitItemGSON.class);

    Log.d("json practice/ name : ",fruitItem.getName());
    for(int i=0; i<fruitItem.getColor().length; i++)
        Log.d("json practice/ color : ",fruitItem.getColor()[i]);

    /**JAVA INSTANCE -> JSON INSTANCE*/
    if(fruitItem!=null){
        String jsonFromObject = gson.toJson(fruitItem);
        Log.d("json practice/ JAVA INSTANCE -> JSON INSTANCE : ",jsonFromObject);
    }

}

정상적으로 추출된 걸 확인할 수 있다.

위의 데이터 형식 이슈만 주의한다면 GSON을 활용하는 게 훨씬 작업하기는 편리한 것 같다.

 

Jackson
 : 구글에서 제공하는 JSON 라이브러리로, 주로 JSON 데이터를 JAVA 객체로 변환할 때 주로 사용한다.

   이외에도 XML/YAML/CSV 등 다양한 형식의 데이터 작업에 사용되며, 사용이 빠르다는 장점이 있음.

 

build.gradle에 의존성 추가

dependencies {
    implementation 'com.fasterxml.jackson.core:jackson-core:2.9.7'
    implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.7'
    implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.7'
    }

 

#1 ObjectMapper.readValue를 이용하여 JSONObject 객체를 JAVA 객체로 변환하기

굉장히 쉽다.

이번에도 String 객체만 이용하여 데이터 파싱을 했지만, mapper를 이용하여

URL, File 방식으로도 데이터를 읽어올 수 있다.

public void parseDataFromJackson(String json) throws IOException {
    ObjectMapper mapper = new ObjectMapper();

    FRUITData.FruitItemGSON fruitItem = mapper.readValue(json,FRUITData.FruitItemGSON.class);

    Log.d("json practice/ name : ",fruitItem.getName());
    for(int i=0; i<fruitItem.getColor().length; i++)
        Log.d("json practice/ color : ",fruitItem.getColor()[i]);

}

매우 잘 됨

 

#2 ObjectMapper.writeValue를 이용하여 JAVA 객체를 JSONObject 객체로 변환하기

 

public void parseDataFromJackson(String json) throws IOException {
    ObjectMapper mapper = new ObjectMapper();

    FRUITData.FruitItemGSON fruitItem = mapper.readValue(json,FRUITData.FruitItemGSON.class);

    Log.d("json practice/ name : ",fruitItem.getName());
    for(int i=0; i<fruitItem.getColor().length; i++)
        Log.d("json practice/ color : ",fruitItem.getColor()[i]);

    /**JAVA INSTANCE -> JSON INSTANCE*/
    if(fruitItem!=null){
        mapper.writeValue(new File("C:\\sample\\output.json"),fruitItem);
    }
}

이것도 한 줄만 추가해서 쉽게 파일을 추출할 수 있었다.

 

Jackson 라이브러리는 다른 라이브러리들에 비해 활용할 수 있는 방향이 다양해보인다.