Web/Django-python

Django 에서 JSON으로 데이터 파싱하고 주고받기

일단 목적은 제목과 같은데, 간단하게 순서를 정리해보자.

  1. 웹페이지에서 form을 통해 데이터 전송을 요청
  2. 파이썬에서 API로부터 JSON데이터를 받아와서 활용하기 쉬운 형태로 파싱함.
  3. 파싱한 데이터를 json 형태로 Django의 view로 전달함.
  4. view는 다시 받아온 json 데이터를 template의 html 파일로 전달함.
  5. 템플릿으로 전달된 json 데이터는 자바스크립트에 의해 활용할 수 있는 형태로 다시 파싱된다.
  6. 이제 자바스크립트를 통해서 json 데이터를 자유롭게 사용할 수 있음.

1.웹페이지에서 form을 통해 데이터 전송을 요청하기

1번부터 차근차근 해보자. 우선 웹 페이지에서 데이터를 받아오게 요청을 보내야한다.

contact.html

<form method="post">{% csrf_token %}
    <input type="text" name="name"> 
    <input type="submit" value="Send message">

views.py

from .apicodes import keyword
~~~~
class FormView(generic.View): #formtest
    template_name = 'apitest/contact.html'

    def post(self,request):
        inputkw = request.POST.get('name')
        result = keyword.keywordFindAPI(inputkw)
        context ={
            'result' : result
        }
        return render(request,self.template_name,context)

일단 템플릿 페이지에서 name이라는 이름으로 입력값을 views.py로 넘겨준다. 넘어가는 값은 기본적으로 post 형태를 가지고 넘어가고, 뷰에서는 request 인자를 가지고 해당 값을 읽어와 사용하게 만든다.
그렇게 넘긴 입력값을 view에서 파이썬 코드로 전달하는데, 이떄 사용할 파이썬 코드는 views.py 에 임포트되어있으면 된다.

2~4. 파이썬에서 API로부터 JSON 데이터를 받아와 활용하기 쉬운 형태로 파싱후 템플릿으로 전달.

keyword.py

#-*- coding:utf-8 -*-
from urllib.parse import urlencode, quote_plus
from urllib.request import urlopen , Request
from .apikey import * #if using on django , should using .apikey instead apikey 
import json 

def keywordFindAPI(inputKeyword):
    url = 'http://api.visitkorea.or.kr/openapi/service/rest/KorService/searchKeyword'
    #find place by keyword API
    queryParams = '?' + 'ServiceKey='+ServiceKey+'&'+urlencode({ 
        quote_plus('MobileApp') : 'AppTest', 
        quote_plus('MobileOS') : 'ETC', quote_plus('pageNo') : '1', quote_plus('numOfRows') : '10', 
        quote_plus('listYN') : 'Y', quote_plus('arrange') : 'A', quote_plus('contentTypeId') : '12', 
        quote_plus('areaCode') : '', quote_plus('sigunguCode') : '', quote_plus('cat1') : '', 
        quote_plus('cat2') : '', quote_plus('cat3') : '', quote_plus('keyword') : inputKeyword 
    })
    request = Request(url + queryParams + "&_type=json")
    request.get_method = lambda: 'GET'
    response_body = urlopen(request).read()

    #make dict datas
    getJson = json.loads(response_body)["response"]["body"]
    retItems = {}
    i=0
    for item in getJson["items"]["item"] :
        item = dict( item.items() )
        retItems['item'+str(i)] = ( json.dumps(item,ensure_ascii=False) )
        i += 1
        #make dict_items type Objects to list type objects
    return retItems #return datas to json data

파이썬에서는 json 데이터를 딕셔너리 타입 객체로 변환하여 사용하기 때문에 , JSON 데이터를 딕셔너리로 변환하는 작업부터 해보자.

파이썬 코드 안에 함수를 선언하여 사용한다. API 를 통해 받아온 데이터는 JSON 형식을 가지는 문자열 형태로 넘어오므로, 파이썬에서 이를 딕셔너리 객체로 사용하기 위해 json.loads 함수를 사용한다.

그 다음, API로 넘어온 데이터는 기본적으로 여러 아이템들이 들어있는 json 데이터이므로, for ~ in 구문을 사용하여 각 아이템을 분리해주는 작업을 거쳐야 한다. 이떄 분리된 아이템들은 list 형태로 저장된다.

우리는 가져온 데이터를 JSON 형식 그대로 다시 views.py로 보내주고 싶다. 아이템들이 리스트 타입으로 저장되기 때문에 이를 JSON 데이터로 변환해야 하는데, 리스트에서 JSON으로 바로 변환이 불가하여 dict로 한번 바꿔준 다음 2차적으로 변환을 거쳐야 한다.

또한 API에서 여러 아이템들을 구분없이 마구 보내주기 때문에, 이를 원활하게 사용하려면 API에서 한꺼번에 보내준 아이템들을 구분시켜 저장해 줄 필요성이 있다. 기왕 리스트->딕셔너리 변환을 거치는 김에 딕셔너리에서 item이라는 이름으로 key를 만들어 데이터를 추가하는 식으로 변환을 진행해주자.

기존 데이터

[{'addr1': '서울특별시 종로구 사직로 161', 'addr2': '(세종로)', 'areacode': 1, 'booktour': 1, 'cat1': 'A02', 'cat2': 'A0201', 'cat3': 'A02010300', 'contentid': 126512, 'contenttypeid': 12, 'createdtime': 20031027000000, 'firstimage': 'http://tong.visitkorea.or.kr/cms/resource/81/1075281_image2_1.jpg', 'firstimage2': 'http://tong.visitkorea.or.kr/cms/resource/81/1075281_image3_1.jpg', 'mapx': 126.9768042386, 'mapy': '37.5760725520', 'mlevel': 6, 'modifiedtime': 20200323134231, 'readcount': 114240, 'sigungucode': 23, 'title': '광화문'},
{'addr1': '서울특별시 종로구 세종대로 172', 'addr2': '(세종로)', 'areacode': 1, 'booktour': 0, 'cat1': 'A02', 'cat2': 'A0202', 'cat3': 'A02020700', 'contentid': 775394, 'contenttypeid': 12, 'createdtime': 20090804175125, 'firstimage': 'http://tong.visitkorea.or.kr/cms/resource/11/1945811_image2_1.jpg', 'firstimage2': 'http://tong.visitkorea.or.kr/cms/resource/11/1945811_image3_1.jpg', 'mapx': 126.9769709861, 'mapy': 37.5727035021, 'mlevel': 6, 'modifiedtime': 20190326130402, 'readcount': 44183, 'sigungucode': 23, 'title': '광화문광장'}]

기존 데이터는 위와 같이 아이템별로 구분할 key가 별도로 존재하지 않는 형태이므로, key값을 가지고 구분하기 위해 데이터에 변형을 거쳐야 한다.

retItems['item'+str(i)] = ( json.dumps(item,ensure_ascii=False) ) 형태로 json.dumps 함수를 사용하면 딕셔너리 타입으로 변환된 item 객체를 다시 json 형태로 변환하여 retItems 딕셔너리 객체의 value로 쓰게 만들고, "item"+str(i) 는 해당 딕셔너리 값의 키가 된다.

즉, 딕셔너리 안에서 json 데이터를 value로 사용하게 만드는 일종의 꼼수.

변환 거친 데이터

{'item0': '{"addr1": "서울특별시 종로구 사직로 161", "addr2": "(세종로)", "areacode": 1, "booktour": 1, "cat1": "A02", "cat2": "A0201", "cat3": "A02010300", "contentid": 126512, "contenttypeid": 12, "createdtime": 20031027000000, "firstimage": "http://tong.visitkorea.or.kr/cms/resource/81/1075281_image2_1.jpg", "firstimage2": "http://tong.visitkorea.or.kr/cms/resource/81/1075281_image3_1.jpg", "mapx": 126.9768042386, "mapy": "37.5760725520", "mlevel": 6, "modifiedtime": 20200323134231, "readcount": 114240, "sigungucode": 23, "title": "광화문"}', 'item1': '{"addr1": "서울특별시 종로구 세종대로 172", "addr2": "(세종로)", "areacode": 1, "booktour": 0, "cat1": "A02", "cat2": "A0202", "cat3": "A02020700", "contentid": 775394, "contenttypeid": 12, "createdtime": 20090804175125, "firstimage": "http://tong.visitkorea.or.kr/cms/resource/11/1945811_image2_1.jpg", "firstimage2": "http://tong.visitkorea.or.kr/cms/resource/11/1945811_image3_1.jpg", "mapx": 126.9769709861, "mapy": 37.5727035021, "mlevel": 6, "modifiedtime": 20190326130402, "readcount": 44183, "sigungucode": 23, "title": "광화문광장"}'}

"item"+숫자 로 된 키를 추가해 줌으로써 딕셔너리, JSON 타입으로 편하게 사용할 수 있게 되었다.

이후엔 데이터를 retItems 딕셔너리 타입 변수에서 item0, item1, item2 등등의 이름을 가지고 사용해줄 수 있다.
이렇게 키가 추가된 딕셔너리 타입 변수는 추가적인 변환 없이도 JSON양식으로 사용할 수 있다.

파싱을 거친 데이터는 views.py를 거쳐 템플릿의 html 파일로 전송된다.

5. 템플릿으로 전달된 json 데이터 파싱

여기부터 애를 많이 먹었는데, django 프레임워크에서 기본적으로 파이썬으로부터 웹페이지로 전달된 코드는 XSS 공격 방지를 위해 필터링이 거쳐진 채로 전달이 된다.

특수문자나 한글이 유니코드형태로 변환되거나, 다른 html에서 사용 가능한 형태로 바뀌기 때문에 자바스크립트에서 이 데이터를 JSON 데이터라고 인식하지를 못한다.

다행히도 장고 템플릿에서 제공하는 json_script 함수를 이용하면 이 문제를 어느정도는 해결할 수 있다.

contact.html

{% if result %}
        {{ result | json_script:'jsonData'}}
        name is : 
        <script type = "text/javascript"> 
            var placeData = JSON.parse(document.getElementById('jsonData').textContent);
            for( var key1 in placeData){
                var jsonData = JSON.parse(placeData[key1])
                   for (var key2 in jsonData)
                {
                    document.write(key2+" : " +jsonData[key2]+"<br>")            
                }
            }
        </script>

    {% endif %}

위 코드에서 {{ result | json_script:'jsonData'}} 부분이 그 역할을 담당한다. 장고로부터 받아온 result 컨텍스트 데이터를 json_script 함수를 거쳐서 자바스크립트 내에서 "jsonData" 라는 이름으로 쓰겠다는 의미이다.
그럼 소스코드에서는 아래와 같은 형태로 변환되어 보인다.

<script id="jsonData" type="application/json">변수 내용</script>

이 값은 JSON 데이터 양식을 그대로 따르는데, 자바스크립트에서 변수를 가져와 값으로 사용하려면 문자열 형태로 가져와야 하므로 , JSON 양식 문자열을 JSON 데이터로 변환해주는 JSON.parse 함수를 이용해 변환을 거쳐주자.

그 후 자바스크립트의 for ~ in 구문을 이용해 (파이썬과 유사하다) 데이터를 파싱해줘야 하는데 이렇게 가져온 데이터는 JSON 객체가 아니라 JSON 양식 문자열 이기 때문에 JSON.parse 함수를 이용해 다시 JSON 객체로 바꿔주어야 JSON 객체로 활용이 가능해진다.

6. 자바스크립트에서 JSON 데이터 활용

for (var key2 in jsonData)
{
    document.write(key2+" : " +jsonData[key2]+"<br>")            
}

위의 템플릿 파일의 마지막 부분이다. jsonData는 JSON 객체이기 때문에 저런 식으로 활용하여 내용을 가져올 수 있게 된다.

그러면 이렇게JSON 데이터의 key와 value를 짝지어 볼 수 있는 페이지가 완성된다.