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
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!"
}
]
}
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(),
};
}
}
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,
};
}
}
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);
}
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);
}
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'.
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
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)
Proje gezginine bakarsak, bazı yeni dosyalar bulabiliriz:
restaurant.dart
restaurant.freezed.dart
restaurant.g.dart
review.dart
review.freezed.dart
review.g.dart
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);
}
@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);
}
Ardından kod oluşturucuyu tekrar çalıştırabiliriz ve gerisini Freezed halleder:
flutter pub run build_runner build --delete-conflicting-outputs
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);
}
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.
Top comments (2)
Faydalı bir makale. Teşekkürler.
Ben teşekkür ederim :)