daino_saur
article thumbnail
반응형

 

JSON 파싱

 

서막

 

이전에 나는 구글의 API를 활용한 글을 쓴 적이 있다.

 

 

플러터 [Flutter] 구글맵 [Googlemap] - json 데이터로 마커 생성(2023ver)

http로 전 세계 구글지사 json 파일을 받아 구글맵에 마커를 생성해 보겠습니다. 패키지 추가 HTTP 요청을 위해 http 패키지 추가 flutter pub add http json으로 받아온 데이터를 직렬화해주는 패키지를 추

daino.tistory.com

 

 

근데 어떤 분이 내가 그 글에 댓글을 달아주셨다.

 

 

JSON 데이터 파싱시, json_serializable 패키지를 사용했는데,

 

데이터가 원할하게 받아오지지 않고 누실이 있다는 내용이었다.

 

사실 나는 주로 Firebase로 내부 데이터만 관리했지 외부 API를 써본 적이 없었다.

 

그렇지만 너무 도와주고 싶은 마음에... 그때부터 구글링에 돌입했다.

 

본론

 

API 구조 파악

 

먼저 API 구조부터 파악을 했다.

 

요청하신 API 주소

https://openapi.gg.go.kr/GGGOODINFLSTOREST?KEY=인증키 값&Type=json&pIndex=1&pSize=100

 

API를 받아온 곳에 사이트에 들어가서 확인을 해보니

 

OpenApi/결식아동데이터?인증키&JSON 타입&페이지 숫자&페이지 사이즈

 

라는 구조를 알게 되었다.

 

 


따라 해보고 싶은 분들은 아래의 사이트에 들어가서 인증키 값 받으시면 됩니다!
 

경기도 선한영향력가게 현황 | 데이터셋 상세 Open API | 경기데이터드림

선한 영향력 가게에서 제공하는 경기도 내 선한 영향력 가게 목록입니다. 선한 영향력 가게는 자발적으로 결식아동을 지원하는 가게 목록으로 음식점 외에도 아이들에게 도움이 될 수 있는 다

data.gg.go.kr

 

JSON 구조 파악

 

어떤 정보를 받아오는지 알았으니 받아온 JSON 데이터 구조를 확인해보았다.

 

API 주소로 다이렉트로 들어가 보니 엄청나게 복잡해 보이는 JSON 데이터가 보였다.

 

direct JSON data

 

구조를 한눈에 보기 어려워 알아보니 JSON Viewer라는 사이트가 있었다.

 

 

Online JSON Viewer and Formatter

 

jsonviewer.stack.hu

 

데이터를 복붙 해 넣어봤다.

 

그리고 이전에 파싱시 사용했던 구글의 JOSN 데이터 구조와 비교해 보았다.

 

Google Data

 

OpenApi Data

 

그리고 구조적으로 차이점이 있음을 깨달았다.

 

바로 google의 json 파일은 ofiices 안에 바로 원하는 데이터 리스트가 있지만

 

Open Api의 json 파일은 GGGOODINFLSTOREST 안에 바로 데이터가 있는 것이 아닌,

 

GGGOODINFLSTOREST 안에 {1} 안에 [row] 안에 원하는 데이터가 있는 것을 알게 되었다.

 

이제 이 내용을 가지고 문제의 코드를 살펴보겠다.

 

문제의 코드

 

shopdata.dart

 

// import 'dart:convert';
// import 'package:json_annotation/json_annotation.dart';
// import 'package:http/http.dart' as http;
// import 'package:flutter/services.dart' show rootBundle;

// part 'shopdata.g.dart';

// @JsonSerializable()
// class Shop {
//   Shop({
//     required this.name, // 가게 이름
//     required this.sigunNm,
//     required this.bsnTmNm,
//     required this.provsnProdlstNm,
//     required this.provsnTrgtNm2,
//     required this.refineLotnoAddr,
//     required this.refineZipno,
//     required this.lat, // 가게 위도
//     required this.lon, // 가게 경도
//     required this.detailAddr,
//     required this.provsnTrgtNm1,
//     required this.adress,
//   });

//   factory Shop.fromJson(Map<String, dynamic> json) => _$ShopFromJson(json);
//   Map<String, dynamic> toJson() => _$ShopToJson(this);

//   final String name;
//   final String sigunNm;
//   final String bsnTmNm;
//   final String provsnProdlstNm;
//   final String provsnTrgtNm2;
//   final String refineLotnoAddr;
//   final String refineZipno;
//   final double lat;
//   final double lon;
//   final String detailAddr;
//   final String provsnTrgtNm1;
//   final String adress;
// }

// @JsonSerializable()
// class Locations {
//   Locations({
//     required this.shops,
//   });

//   factory Locations.fromJson(Map<String, dynamic> json) =>
//       _$LocationsFromJson(json);

//   Map<String, dynamic> toJson() => _$LocationsToJson(this);

//   final List<Shop> shops;
// }

// Future<Locations> fetchShops() async {
//   const url =
//       'https://openapi.gg.go.kr/GGGOODINFLSTOREST?KEY=인증키&Type=json&pSize=1000';

//   try {
//     final response = await http.get(Uri.parse(url));
//     if (response.statusCode == 200) {
//       return Locations.fromJson(json.decode(response.body));
//     }
//   } catch (e) {
//     print(e);
//   }

//   // Fallback for when the above HTTP request fails.
//   return Locations.fromJson(
//     json.decode(
//       await rootBundle.loadString('assets/shopdata.json'),
//     ),
//   );
// }

import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/services.dart' show rootBundle;

part 'shopdata.g.dart';

@JsonSerializable()
class Shop {
  final String cmpnmNm;
  final String sigunNm;
  final String bsnTmNm;
  final String provsnProdlstNm;
  final String provsnTrgtNm2;
  final String refineLotnoAddr;
  final String refineZipno;
  final double refineWgs84Lat;
  final double refineWgs84Logt;
  final String detailAddr;
  final String provsnTrgtNm1;
  final String refineRoadnmAddr;

  Shop({
    required this.cmpnmNm, // 가게 이름
    required this.sigunNm,
    required this.bsnTmNm,
    required this.provsnProdlstNm,
    required this.provsnTrgtNm2,
    required this.refineLotnoAddr,
    required this.refineZipno,
    required this.refineWgs84Lat, // 가게 위도
    required this.refineWgs84Logt, // 가게 경도
    required this.detailAddr,
    required this.provsnTrgtNm1,
    required this.refineRoadnmAddr,
  });

  factory Shop.fromJson(Map<String, dynamic> json) => _$ShopFromJson(json);
  Map<String, dynamic> toJson() => _$ShopToJson(this);
}

@JsonSerializable()
class Locations {
  Locations({
    required this.shops,
  });

  factory Locations.fromJson(Map<String, dynamic> json) =>
      _$LocationsFromJson(json);

  Map<String, dynamic> toJson() => _$LocationsToJson(this);

  final List<Shop> shops;
}

Future<Locations> fetchShops() async {
  const url =
      'https://openapi.gg.go.kr/GGGOODINFLSTOREST?KEY=fe1900a2f97b485cb3f61a0368d4baf4&Type=json&pIndex=1&pSize=100';

  try {
    final response = await http.get(Uri.parse(url));
    if (response.statusCode == 200) {
      return Locations.fromJson(json.decode(response.body));
    }
  } catch (e) {
    print(e);
  }

  // Fallback for when the above HTTP request fails.
  return Locations.fromJson(
    json.decode(
      await rootBundle.loadString('assets/shopdata.json'),
    ),
  );
}

 

shopdata.g.dart

 

// GENERATED CODE - DO NOT MODIFY BY HAND

part of 'shopdata.dart';

// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************

Shop _$ShopFromJson(Map<String, dynamic> json) => Shop(
      cmpnmNm: json['cmpnmNm'] as String,
      sigunNm: json['sigunNm'] as String,
      bsnTmNm: json['bsnTmNm'] as String,
      provsnProdlstNm: json['provsnProdlstNm'] as String,
      provsnTrgtNm2: json['provsnTrgtNm2'] as String,
      refineLotnoAddr: json['refineLotnoAddr'] as String,
      refineZipno: json['refineZipno'] as String,
      refineWgs84Lat: (json['refineWgs84Lat'] as num).toDouble(),
      refineWgs84Logt: (json['refineWgs84Logt'] as num).toDouble(),
      detailAddr: json['detailAddr'] as String,
      provsnTrgtNm1: json['provsnTrgtNm1'] as String,
      refineRoadnmAddr: json['refineRoadnmAddr'] as String,
    );

Map<String, dynamic> _$ShopToJson(Shop instance) => <String, dynamic>{
      'cmpnmNm': instance.cmpnmNm,
      'sigunNm': instance.sigunNm,
      'bsnTmNm': instance.bsnTmNm,
      'provsnProdlstNm': instance.provsnProdlstNm,
      'provsnTrgtNm2': instance.provsnTrgtNm2,
      'refineLotnoAddr': instance.refineLotnoAddr,
      'refineZipno': instance.refineZipno,
      'refineWgs84Lat': instance.refineWgs84Lat,
      'refineWgs84Logt': instance.refineWgs84Logt,
      'detailAddr': instance.detailAddr,
      'provsnTrgtNm1': instance.provsnTrgtNm1,
      'refineRoadnmAddr': instance.refineRoadnmAddr,
    };

Locations _$LocationsFromJson(Map<String, dynamic> json) => Locations(
      shops: (json['shops'] as List<dynamic>)
          .map((e) => Shop.fromJson(e as Map<String, dynamic>))
          .toList(),
    );

Map<String, dynamic> _$LocationsToJson(Locations instance) => <String, dynamic>{
      'shops': instance.shops,
    };

 

식별하기 어려운 네이밍

 

코드를 좀 쳐보신 개발자분이라면 벌써 불편한 부분이 있을 것 같다.

 

바로 네이밍이 식별하기 어렵다는 것이다.

 

Shop 클래스 선언 시 데이터의 변수의 이름을 JSON 키 값의 이름 그대로 지정해 주셨다.

 

변수의 네이밍_전JOSN 파일의 key&amp;#44;value
네이밍

 

물론 이건 API를 만든 개발자가 좋게 만들어 줬으면 좋지만 그렇지 않았다면 사용자가 변경을 해줘야 한다.

 

네이밍이 식별하기 어렵다면, 내가 원하는 데이터를 계속해서 다시 JSON 파일을 보면서 확인해야 할 수 있음으로

 

미리 클래스 선언 시 변수의 이름을 보기 쉽게 변경해 주면 된다.

 

나는 가게 이름과 위도, 경도, 도로명 주소만 사용할 것이기에 name, lat, lon, address로 바꿔주었다.

 

네이밍_후

 

정확하지 않은 키 값

 

json_serializable을 사용해 파싱파일을 만들면 json의 키 값은 이전에 선언한 변수의 이름을 따라간다.

 

json_serializable 생성자
좌:shopdata.dart / 우: shopdata.g.dart

 

하지만 처음 변수 생성 시 네이밍을 키 값 그대로 사용해주시지 않았기에 josn 파싱시 올바른 키 값을 찾지 못한다.

 

수정을 위해 json 키값을 원래와 맞게 변경해 준다.

 

json 파일의 키값 키 값 변경
josn 파일의 키값과 동일하게 지정

 

정확한 Json 파일 접근

 

아까 위에서 GGGOODINFLSTOREST 안에 {1} 안에 [row] 안에 원하는 데이터가 있다고 했다.

 

json 파싱을 위해서는 원하는 데이터가 있는 곳까지 접근해줘야 한다.

 

원하는 데이터가 있는곳까지 접근해줘야한다.

 

현재는 shops에서 데이터 관찰을 하는 것을 알 수 있다.

 

이것을 우리가 원하는 데이터가 있는 곳까지 접근시켜 준다.

 

정확한 데이터 접근

 

이후 shops를 출력해 보면 올바른 값이 나온다.

 

null 체크

 

받아오는 데이터중 null 값이 있을 수 있음으로 미리 기본값을 지정해 준다.

 

반응형

'프로그래밍 > Flutter' 카테고리의 다른 글

Flutter의 상태관리  (0) 2024.05.15
[Flutter] HTTP 구조  (0) 2023.10.29
[Flutter] Lambdas  (0) 2023.10.29
[Flutter] Future  (0) 2023.10.16
[Flutter] Callback Function  (0) 2023.10.16
profile

daino_saur

@daino

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!