DEV Community

Cover image for Freezed Kullanarak Flutter'da JSON Nasıl Ayrıştırılır? 💫 🌌 ✨
Gülsen Keskin
Gülsen Keskin

Posted on • Updated on

Freezed Kullanarak Flutter'da JSON Nasıl Ayrıştırılır? 💫 🌌 ✨

Not: Bu yazı Andrea Bizzotto'nun makalesinin Türkçe çevirisi niteliğini taşır. Orijinal içeriğe linkten ulaşabilirsiniz.

Bir önceki yazıda, Dart'ta JSON'ı type-safe model sınıflarına nasıl ayrıştıracağımızı öğrendik.

Ancak, çok sayıda model sınıfımız varsa, tüm JSON ayrıştırma kodunu elle yazmak zaman alıcı ve hataya açık hale gelir. Neyse ki, süreci otomatikleştirmek için json_serializable ve Freezed gibi kod oluşturma araçlarını kullanabiliriz .

Bu yazıda, Freezed paketini kullanarak kod oluşturma (code generation) ile JSON verilerinin nasıl ayrıştırılacağını öğreneceğiz .

Öncelikle pubspec.yaml dosyasına aşağıdaki bağımlılıkları eklememiz gerekiyor.

dependencies:
  flutter:
    sdk: flutter
  freezed_annotation: ^1.1.0
  json_annotation: ^4.4.0

dev_dependencies:
  build_runner: ^2.1.7
  freezed: ^1.1.1
  json_serializable: ^6.1.4

Enter fullscreen mode Exit fullscreen mode

json_serializable: Dart sınıflarına açıklama ekleyerek JSON'a ve JSON'dan dönüştürme için otomatik olarak kod oluşturun

json_annotation: "json_serializable" paketi aracılığıyla JSON kodu oluşturmayı destekleyen sınıflar ve yardımcı işlevler sağlar.

freezed: basit bir API ile karmaşık kullanım durumlarını işleyebilen güçlü bir kod oluşturucu.

freezed_annotation: freezed tarafından kullanılan açıklamaları tanımlar.

build_runner: Dart dosyaları oluşturabilen bağımsız bir derleme paketidir.

JSON ayrıştırma kodunu yalnızca json_serializable ile (freezed kullanmadan) oluşturabilirsiniz. Ancak, freezed daha güçlüdür ve basit bir API ile karmaşık kullanım durumlarının üstesinden gelebilir.

Örnek bir JSON belgesi

İşleri önceki makaleyle uyumlu tutmak için aynı JSON örneğini yeniden kullanacağız:

{
  "name": "Pizza da Mario",
  "cuisine": "Italian",
  "year_opened": 1990,
  "reviews": [
    {
      "score": 4.5,
      "review": "The pizza was amazing!"
    },
    {
      "score": 5.0,
      "review": "Very friendly staff, excellent service!"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Daha önce yazdığımız Restaurant ve Review model sınıfları şunlardır:

class Restaurant {
  Restaurant({
    required this.name,
    required this.cuisine,
    this.yearOpened,
    required this.reviews,
  });
  final String name;
  final String cuisine;
  final int? yearOpened;
  final List<Review> reviews;

  factory Restaurant.fromMap(Map<String, dynamic> data) {
    final name = data['name'] as String;
    final cuisine = data['cuisine'] as String;
    final yearOpened = data['year_opened'] as int?;
    final reviewsData = data['reviews'] as List<dynamic>?;
    final reviews = reviewsData != null
        ? reviewsData.map((reviewData) => Review.fromMap(reviewData)).toList()
        : <Review>[];
    return Restaurant(
      name: name,
      cuisine: cuisine,
      yearOpened: yearOpened,
      reviews: reviews,
    );
  }

  Map<String, dynamic> toMap() {
    return {
      'name': name,
      'cuisine': cuisine,
      if (yearOpened != null) 'year_opened': yearOpened,
      'reviews': reviews.map((review) => review.toMap()).toList(),
    };
  }
}
Enter fullscreen mode Exit fullscreen mode
class Review {
  Review({required this.score, this.review});
  final double score;
  // nullable - assuming the review may be missing
  final String? review;

  factory Review.fromMap(Map<String, dynamic> data) {
    final score = data['score'] as double;
    final review = data['review'] as String?;
    return Review(score: score, review: review);
  }

  Map<String, dynamic> toMap() {
    return {
      'score': score,
      if (review != null) 'review': review,
    };
  }
}
Enter fullscreen mode Exit fullscreen mode

Görüldüğü gibi çok fazla kod var ve birçok faklı modelimiz varsa bu yaklaşım ölçeklenemez.

Freezed ile Model Sınıfları

Hayatımızı kolaylaştırmak için, Restaurant ve Review model sınıflarımızı tanımlamak için Freezed'i kullanalım.

Restaurant Review modeline bağlı olduğundan, Review sınıfıyla başlayalım:

// review.dart
// 1. import freezed_annotation
import 'package:freezed_annotation/freezed_annotation.dart';

// 2. 'part' dosyalarını ekle
part 'review.freezed.dart';
part 'review.g.dart';

// 3. @freezed annotation ekle
@freezed
// 4. mixin ile bir sınıf tanımlayın
class Review with _$Review {
  // 5. bir factory constructor tanımlayın
  factory Review({
    // 6. tüm argümanları/özellikleri listele
    required double score,
    String? review,
  }) = _Review;

  // 7. json'dan ayrıştırılacak başka bir factory constructor tanımlayın
  factory Review.fromJson(Map<String, dynamic> json) => _$ReviewFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode

Aynısını Restaurant sınıfı için de yapalım:

// restaurant.dart
import 'package:freezed_annotation/freezed_annotation.dart';
// bağımlı olduğumuz diğer modelleri içe aktarın
import 'review.dart';

part 'restaurant.freezed.dart';
part 'restaurant.g.dart';

@freezed
class Restaurant with _$Restaurant {
  factory Restaurant({
    required String name,
    required String cuisine,

    @JsonKey(name: 'year_opened') int? yearOpened,
    // not: varsayılan değer olarak boş bir liste kullanmak
    @Default([]) List<Review> reviews,
  }) = _Restaurant;

  factory Restaurant.fromJson(Map<String, dynamic> json) =>
      _$RestaurantFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode

Hem Restaurant hem de Review sınıflarının, ihtiyacımız olan tüm argümanları listeleyen bir factory constructor'a sahip olduğuna dikkat edin, ancak ilgili özellikleri bildirmedik.

Aslında kodumuz eksik ve aşağıdaki gibi hatalar üretecek:

Target of URI doesn't exist: 'restaurant.freezed.dart'.
Try creating the file referenced by the URI, or Try using a URI for a file that does exist.

The name '_Restaurant' isn't a type and can't be used in a redirected constructor.
Try redirecting to a different constructor.

The method '_$RestaurantFromJson' isn't defined for the type 'Restaurant'.
Try correcting the name to the name of an existing method, or defining a method named '_$RestaurantFromJson'.
Enter fullscreen mode Exit fullscreen mode

Kod oluşturucuyu çalıştırma

Eksik kodu oluşturmak için bunu konsolda çalıştırabiliriz:

flutter pub run build_runner build --delete-conflicting-outputs
Enter fullscreen mode Exit fullscreen mode

Bu, aşağıdaki çıktıyı üretecektir:

[INFO] Generating build script...
[INFO] Generating build script completed, took 419ms

[INFO] Initializing inputs
[INFO] Reading cached asset graph...
[INFO] Reading cached asset graph completed, took 55ms

[INFO] Checking for updates since last build...
[INFO] Checking for updates since last build completed, took 428ms

[INFO] Running build...
[INFO] 1.3s elapsed, 0/2 actions completed.
[INFO] Running build completed, took 2.1s

[INFO] Caching finalized dependency graph...
[INFO] Caching finalized dependency graph completed, took 27ms

[INFO] Succeeded after 2.1s with 5 outputs (5 actions)
Enter fullscreen mode Exit fullscreen mode

Proje gezginine bakarsak, bazı yeni dosyalar bulabiliriz:

restaurant.dart
restaurant.freezed.dart
restaurant.g.dart
review.dart
review.freezed.dart
review.g.dart
Enter fullscreen mode Exit fullscreen mode

Her model sınıfı için kod oluşturucu şunları ekler:

• toString() yöntemi
• == operatörü
• hashCode getter değişkeni
• copyWith() yöntemi
• toJson() yöntemi

Ve eğer model sınıflarımızdaki özelliklerden herhangi birini değiştirmemiz gerekirse, onların factory constructor'larını güncellememiz yeterlidir:

@freezed
class Review with _$Review {
  factory Review({
    // update any properties as needed
    required double score,
    String? review,
  }) = _Review;

  factory Review.fromJson(Map<String, dynamic> json) => _$ReviewFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode
@freezed
class Restaurant with _$Restaurant {
  factory Restaurant({
    // update any properties as needed
    required String name,
    required String cuisine,
    @JsonKey(name: 'year_opened') int? yearOpened,
    @Default([]) List<Review> reviews,
  }) = _Restaurant;

  factory Restaurant.fromJson(Map<String, dynamic> json) =>
      _$RestaurantFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode

Ardından kod oluşturucuyu tekrar çalıştırabiliriz ve gerisini Freezed halleder:

flutter pub run build_runner build --delete-conflicting-outputs
Enter fullscreen mode Exit fullscreen mode

Artık birkaç satırlık kodda güvenli , değişmez model sınıfları tanımlayabilir ve tek bir komut çalıştırarak tüm JSON serileştirme kodunu oluşturabiliriz.

JSON ek açıklamaları

Freezed , kod oluşturucunun modellerimizi nasıl işlediğini özelleştirmemize izin veren birçok ek açıklamayı destekler .

En kullanışlı olanlar @JsonKey ve @Default dır

@freezed
class TMDBMovieBasic with _$TMDBMovieBasic {
  factory TMDBMovieBasic({
    @JsonKey(name: 'vote_count') int? voteCount,
    required int id,
    @Default(false) bool video,
    @JsonKey(name: 'vote_average') double? voteAverage,
    required String title,
    double? popularity,
    @JsonKey(name: 'poster_path') required String posterPath,
    @JsonKey(name: 'original_language') String? originalLanguage,
    @JsonKey(name: 'original_title') String? originalTitle,
    @JsonKey(name: 'genre_ids') List<int>? genreIds,
    @JsonKey(name: 'backdrop_path') String? backdropPath,
    bool? adult,
    String? overview,
    @JsonKey(name: 'release_date') String? releaseDate,
  }) = _TMDBMovieBasic;

  factory TMDBMovieBasic.fromJson(Map<String, dynamic> json) =>
      _$TMDBMovieBasicFromJson(json);
}
Enter fullscreen mode Exit fullscreen mode

Freezed'e hangi anahtarların hangi özelliklerle eşlendiğini söylemek için @JsonKey ek açıklamasını kullanabiliriz.

Null yapılamayan belirli bir özellik için varsayılan bir değer belirtmek istiyorsak @Default ek açıklamasını kullanabiliriz.

Kod Oluşturma (Code Generation) Dezavantajları

Kod oluşturmanın bazı açık faydaları vardır ve çok sayıda model sınıfınız varsa, gitmeniz gereken yol budur.

Ama bazı dezavantajları da vardır:

Bir sürü ekstra kod
Restaurant ve Review model sınıflarımız çok basittir, ancak oluşturulan kod 450 satır yer kaplar. Bu, çok sayıda model sınıfınız varsa hızla eklenir.

Kod oluşturma yavaştır

Dart'ta kod oluşturma oldukça yavaştır.

Bunu azaltmanın yolları olsa da, codegen (code generation) büyük projelerde geliştirme iş akışınızı önemli ölçüde yavaşlatabilir.

Oluşturulan dosyalar git'e eklenmeli mi?

Bir ekipte çalışıyorsanız ve oluşturulan dosyaları git'e bağlıyorsanız, çekme isteklerini gözden geçirmek zorlaşır.

Ancak bunu yapmazsanız, proje varsayılan olarak çalıştırılabilir durumda değildir ve:

her ekip üyesinin codegen adımını çalıştırmayı hatırlaması gerekir (potansiyel olarak tutarsızlıklara yol açar)
uygulamayı oluşturmak için özel bir CI oluşturma adımı gereklidir
Ve oluşturulan dosyaların git'e eklenip eklenmeyeceği konusunda henüz bir fikir birliği yoktur.

Sonuç

JSON serileştirme için çeşitli seçenekleri araştırdık.

En iyi yaklaşımı seçmenize yardımcı olacak bazı yönergeler şunlardır:

Birkaç küçük model sınıfınız varsa, JSON ayrıştırma kodunu elle yazabilirsiniz.
Birçok model sınıfınız varsa, Freezed sizin için heavy-lifting yapabilir.
işleri daha da hızlandırmak için, VS Code için Json to Dart Model uzantısını da kullanabilirsiniz.

resource

Oldest comments (2)

Collapse
 
wisecolt profile image
wisecolt

Faydalı bir makale. Teşekkürler.

Collapse
 
gulsenkeskin profile image
Gülsen Keskin

Ben teşekkür ederim :)