Serialization: Jackson ve Gson ile JSON
Bir Java nesnesi RAM'de yaşar — onu bir dosyaya kaydetmek, ağ üzerinden göndermek veya bir API'dan gelen veriyi nesneye dönüştürmek istediğinde bir "çeviri" işlemine ihtiyacın olur. Bu çeviri işlemine serialization (nesne → veri) ve deserialization (veri → nesne) denir.
Modern Java dünyasında bu işlem neredeyse her zaman JSON formatıyla yapılır. Bu derste Java'nın iki büyük JSON kütüphanesini — Jackson ve Gson — derinlemesine öğreneceğiz.
1. Serialization Nedir?
Serialization, bir nesneyi taşınabilir bir formata dönüştürmektir. Deserialization ise tam tersidir — taşınabilir formatı tekrar nesneye çevirir.
🎯 Analoji — Mobilya ve Kargo:
>
IKEA'dan bir dolap aldığını düşün. Mağazada dolap monte edilmiş halde duruyor — ama sana kargo ile göndermesi lazım. Dolabı olduğu gibi kutuya koyamazlar. Önce parçalara ayırır, bir montaj kılavuzu ekler ve düz bir kutuya paketlerler. Sen de eve gelince kılavuza bakarak dolabı tekrar monte edersin. Serialization tam olarak bu: nesneyi (dolabı) taşınabilir bir formata (düz kutu) dönüştürme. Deserialization ise o kutudan dolabı tekrar monte etme.
Neden JSON?
JSON (JavaScript Object Notation) hem insanlar hem makineler tarafından kolayca okunur. Alternatifler var — XML, Protocol Buffers, MessagePack — ama JSON, REST API'ların fiili standardıdır.
{
"name": "Ahmet",
"age": 25,
"email": "ahmet@mail.com",
"roles": ["admin", "user"]
}XML aynı veriyi çok daha fazla karakterle ifade eder. Protocol Buffers daha hızlıdır ama binary format olduğu için insanlar tarafından okunamaz. JSON tam ortadadır — okunabilir ve yeterince hızlı.
2. Java'nın Built-in Serializable'ı
Java'nın kendi serialization mekanizması (java.io.Serializable) 1997'den beri var. Bir sınıfa implements Serializable eklerseniz o sınıfın nesneleri binary formata dönüştürülebilir.
import java.io.*;
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
}
// Serialization — nesneyi dosyaya yaz
User user = new User("Ahmet", 25);
try (var out = new ObjectOutputStream(new FileOutputStream("user.dat"))) {
out.writeObject(user);
}
// Deserialization — dosyadan nesneyi oku
try (var in = new ObjectInputStream(new FileInputStream("user.dat"))) {
User loaded = (User) in.readObject();
}Neden Artık Önerilmiyor?
Java'nın kendi serialization'ı ciddi sorunlar taşır:
Güvenlik açıkları: Deserialization sırasında keyfi kod çalıştırılabilir (Remote Code Execution). Bu, Java tarihindeki en büyük güvenlik sorunlarından biridir.
Versiyon uyumsuzluğu: Sınıfta bir alan ekleyip çıkardığınızda eski serialized verileri okuyamazsınız (
InvalidClassException).Binary format: İnsan tarafından okunamaz, debug etmek zordur.
Platform bağımlılığı: Sadece Java uygulamaları arasında çalışır.
Joshua Bloch (Java'nın mimarlarından) Effective Java kitabında "Java serialization'ı kullanmaktan kaçının" der. Modern Java'da JSON kütüphaneleri (Jackson, Gson) hem daha güvenli hem daha esnektir.
⚠️ Dikkat: Yeni projelerde java.io.Serializable kullanmayın. Mevcut kodda görürseniz bilin ki eski bir mekanizma — JSON tabanlı alternatiflere geçiş yapın.
3. Jackson — Java'nın JSON Şampiyonu
Jackson, Java ekosisteminin en yaygın JSON kütüphanesidir. Spring Boot varsayılan olarak Jackson kullanır. Hızlıdır, esnek annotation desteği vardır ve çok geniş tip desteği sağlar.
Maven Bağımlılığı
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>jackson-databind modülü jackson-core ve jackson-annotations modüllerini transitive olarak çeker — ayrıca eklemenize gerek yoktur.
ObjectMapper — Jackson'ın Kalbi
Tüm JSON işlemleri ObjectMapper sınıfı üzerinden yapılır.
import com.fasterxml.jackson.databind.ObjectMapper;
class User {
private String name;
private int age;
private String email;
// Jackson için no-arg constructor gerekli
public User() { }
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
// Getter'lar gerekli — Jackson bunları kullanarak serialize eder
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
// Setter'lar gerekli — Jackson bunları kullanarak deserialize eder
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
public void setEmail(String email) { this.email = email; }
}ObjectMapper mapper = new ObjectMapper();
// Serialization: Java nesnesi → JSON string
User user = new User("Ayşe", 28, "ayse@mail.com");
String json = mapper.writeValueAsString(user);
System.out.println(json);
// {"name":"Ayşe","age":28,"email":"ayse@mail.com"}
// Deserialization: JSON string → Java nesnesi
String incoming = """
{"name":"Mehmet","age":32,"email":"mehmet@mail.com"}
""";
User parsed = mapper.readValue(incoming, User.class);
System.out.println(parsed.getName()); // Mehmet
// Dosyaya yazma / dosyadan okuma
mapper.writeValue(new File("user.json"), user);
User fromFile = mapper.readValue(new File("user.json"), User.class);Jackson varsayılan olarak getter/setter üzerinden çalışır. getName() metodu varsa JSON'da name alanı oluşur. Getter yoksa o alan serialize edilmez, setter yoksa deserialize edilmez.
Pretty Print
Varsayılan JSON çıktısı tek satırdır. Okunabilirlik için:
ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);
String prettyJson = mapper.writeValueAsString(user);
System.out.println(prettyJson);
// {
// "name" : "Ayşe",
// "age" : 28,
// "email" : "ayse@mail.com"
// }Bilinmeyen Alanları Yok Sayma
API'dan gelen JSON'da sınıfınızda karşılığı olmayan alanlar olabilir. Varsayılan olarak Jackson hata fırlatır. Bunu kapatmak için:
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);Bu ayar, özellikle dış API'larla çalışırken neredeyse her zaman gereklidir. API'ya yeni bir alan eklendiğinde uygulamanız kırılmamalıdır.
4. Jackson Annotations
Jackson'ın asıl gücü annotation'lardadır. Bu annotation'lar serialization/deserialization davranışını ince ayar yapmanızı sağlar.
@JsonProperty — Alan Adı Değiştirme
class ApiUser {
@JsonProperty("user_name")
private String name;
@JsonProperty("user_age")
private int age;
// getter/setter...
}
// Çıktı: {"user_name":"Ahmet","user_age":25}API'lar genellikle snake_case kullanır, Java ise camelCase. @JsonProperty bu uyumsuzluğu çözer.
@JsonIgnore — Alanı Hariç Tutma
class UserResponse {
private String name;
private String email;
@JsonIgnore
private String password; // JSON'da görünmeyecek
@JsonIgnore
private String internalToken; // Bu da görünmeyecek
// getter/setter...
}
// Çıktı: {"name":"Ahmet","email":"ahmet@mail.com"}
// password ve internalToken JSON'da yer almazHassas veriler (şifre, token, internal ID) asla API response'unda yer almamalıdır. @JsonIgnore bunu garanti eder.
@JsonFormat — Tarih Formatlama
import java.time.LocalDateTime;
class Event {
private String title;
@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
private LocalDateTime startTime;
// getter/setter...
}
// Çıktı: {"title":"Konferans","startTime":"15/03/2025 14:30"}Jackson Java 8 tarih tiplerini desteklemek için ek modül gerektirir:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.17.0</version>
</dependency>ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);@JsonCreator — İmmutable Nesneler
No-arg constructor'ı olmayan (immutable) sınıflar için @JsonCreator kullanılır:
class Money {
private final String currency;
private final double amount;
@JsonCreator
Money(@JsonProperty("currency") String currency,
@JsonProperty("amount") double amount) {
this.currency = currency;
this.amount = amount;
}
public String getCurrency() { return currency; }
public double getAmount() { return amount; }
}
// Deserialization: {"currency":"TRY","amount":100.50} → Money nesnesi@JsonCreator Jackson'a "bu constructor'ı kullan" der. Her parametre @JsonProperty ile JSON alanına eşlenir. Bu pattern, record sınıflar ve immutable DTO'lar için idealdir.
@JsonInclude — Null Alanları Atlama
@JsonInclude(JsonInclude.Include.NON_NULL)
class UserProfile {
private String name;
private String email;
private String phone; // null olabilir
// getter/setter...
}
UserProfile profile = new UserProfile();
profile.setName("Ahmet");
profile.setEmail("ahmet@mail.com");
// phone set edilmedi — null
// Çıktı: {"name":"Ahmet","email":"ahmet@mail.com"}
// phone null olduğu için JSON'da yer almaz5. Nested Objeler, Koleksiyonlar ve Generic Tipler
Gerçek dünyada JSON verileri genellikle iç içe nesneler, listeler ve karmaşık yapılar içerir.
Nested (İç İçe) Objeler
class Address {
private String city;
private String street;
private int zipCode;
// constructor, getter, setter...
}
class Customer {
private String name;
private Address address; // nested obje
private List<String> phones; // liste
// constructor, getter, setter...
}String json = """
{
"name": "Zeynep",
"address": {
"city": "İstanbul",
"street": "Bağdat Caddesi",
"zipCode": 34710
},
"phones": ["555-1234", "555-5678"]
}
""";
ObjectMapper mapper = new ObjectMapper();
Customer customer = mapper.readValue(json, Customer.class);
System.out.println(customer.getAddress().getCity()); // İstanbul
System.out.println(customer.getPhones().get(0)); // 555-1234Jackson nested objeleri otomatik olarak doğru tipe dönüştürür. address alanını Address sınıfına, phones alanını List<String>'e çevirir.
Generic Tipler ve TypeReference
Generic tiplerle çalışırken Java'nın type erasure'u devreye girer — List<User> runtime'da sadece List olur. Jackson'ın generic tipi bilmesi için TypeReference kullanılır.
String usersJson = """
[
{"name":"Ahmet","age":25,"email":"ahmet@mail.com"},
{"name":"Ayşe","age":28,"email":"ayse@mail.com"}
]
""";
// Yanlış — generic tip bilgisi kaybolur
// List<User> users = mapper.readValue(usersJson, List.class);
// Bu List<LinkedHashMap> döndürür, List<User> değil!
// Doğru — TypeReference ile generic tip korunur
List<User> users = mapper.readValue(usersJson,
new TypeReference<List<User>>() {});
System.out.println(users.get(0).getName()); // Ahmet
System.out.println(users.get(1).getAge()); // 28Map Dönüşümü
JSON'un Java tarafında tam bir karşılığı yoksa Map kullanılabilir:
String dynamicJson = """
{"status":"ok","count":3,"debug":true}
""";
Map<String, Object> map = mapper.readValue(dynamicJson,
new TypeReference<Map<String, Object>>() {});
System.out.println(map.get("status")); // ok
System.out.println(map.get("count")); // 3💡 İpucu: TypeReference yerine Jackson 2.17+ sürümlerinde mapper.readValue(json, mapper.getTypeFactory().constructCollectionType(List.class, User.class)) kullanılabilir. Ancak TypeReference daha okunabilir ve yaygın tercih edilen yoldur.
6. Gson — Google'ın JSON Kütüphanesi
Gson, Google tarafından geliştirilen JSON kütüphanesidir. Jackson'a göre daha basit bir API sunar ve daha az konfigürasyon gerektirir.
Maven Bağımlılığı
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>Temel Kullanım
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
class Product {
private String name;
private double price;
private boolean inStock;
Product(String name, double price, boolean inStock) {
this.name = name;
this.price = price;
this.inStock = inStock;
}
}Gson gson = new Gson();
// Serialization: Java nesnesi → JSON
Product product = new Product("Laptop", 25000, true);
String json = gson.toJson(product);
System.out.println(json);
// {"name":"Laptop","price":25000.0,"inStock":true}
// Deserialization: JSON → Java nesnesi
String incoming = """
{"name":"Mouse","price":500.0,"inStock":false}
""";
Product parsed = gson.fromJson(incoming, Product.class);
System.out.println(parsed.name); // MouseDikkat: Gson getter/setter gerektirmez — doğrudan alanlara reflection ile erişir. Bu, Gson'ın Jackson'dan temel farklarından biridir. No-arg constructor da genelde gereklidir (Gson Unsafe ile oluşturma yapabilir ama bu önerilmez).
GsonBuilder — Yapılandırma
Gson gson = new GsonBuilder()
.setPrettyPrinting() // Güzel formatlama
.setDateFormat("dd/MM/yyyy") // Tarih formatı
.serializeNulls() // Null alanları dahil et
.setFieldNamingPolicy( // camelCase → snake_case
FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
Product product = new Product("Laptop", 25000, true);
System.out.println(gson.toJson(product));
// {
// "name": "Laptop",
// "price": 25000.0,
// "in_stock": true
// }Gson ile Generic Tipler
Gson'da TypeReference yerine TypeToken kullanılır:
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
String json = """
[
{"name":"Laptop","price":25000.0,"inStock":true},
{"name":"Mouse","price":500.0,"inStock":false}
]
""";
Type listType = new TypeToken<List<Product>>() {}.getType();
List<Product> products = gson.fromJson(json, listType);
System.out.println(products.size()); // 2
System.out.println(products.get(0).name); // LaptopGson Annotation'ları
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
class ApiResponse {
@SerializedName("status_code")
private int statusCode;
@SerializedName("user_name")
private String userName;
@Expose(serialize = false) // Serialize edilmez
private String secret;
}@Expose annotation'ının çalışması için GsonBuilder().excludeFieldsWithoutExposeAnnotation() aktif edilmelidir.
7. Jackson vs Gson Karşılaştırma
| Özellik | Jackson | Gson |
|---|---|---|
| Geliştirici | FasterXML | |
| Hız | Genellikle daha hızlı | Jackson'dan yavaş |
| Spring Boot desteği | Varsayılan | Manuel eklenmeli |
| Annotation zenginliği | Çok zengin | Temel düzey |
| Getter/Setter gerekli mi? | Evet (varsayılan) | Hayır (reflection ile direkt alan erişimi) |
| No-arg constructor | Genellikle gerekli | Genellikle gerekli |
| Streaming API | Var (JsonParser/JsonGenerator) | Var (JsonReader/JsonWriter) |
| Modül sistemi | Zengin (JSR-310, Kotlin, XML) | Sınırlı |
| Dosya boyutu | ~2.5 MB (databind + core + annotations) | ~300 KB |
| Immutable sınıflar | @JsonCreator ile | Zor, TypeAdapter gerekebilir |
| Null handling | Varsayılan: null alanlar dahil edilmez | Varsayılan: null alanlar dahil edilmez |
Hangisini Seçmeli?
Spring Boot projesi: Jackson zaten dahil — başka bir şey eklemeyin
Android projesi: Gson daha küçük boyutu ve basitliğiyle tercih edilir
Yüksek performans: Jackson genellikle daha hızlı, özellikle büyük verilerde
Basit proje: Gson'ın öğrenme eğrisi daha düşük, hızlı başlamak için ideal
8. Custom Serializer / Deserializer
Bazen varsayılan serialization davranışı yeterli olmaz. Örneğin bir Money nesnesini "100.50 TRY" formatında serialize etmek veya API'dan gelen özel bir formatı parse etmek isteyebilirsiniz.
Jackson Custom Serializer
import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
class Money {
private String currency;
private double amount;
Money(String currency, double amount) {
this.currency = currency;
this.amount = amount;
}
public String getCurrency() { return currency; }
public double getAmount() { return amount; }
}class MoneySerializer extends JsonSerializer<Money> {
@Override
public void serialize(Money money, JsonGenerator gen,
SerializerProvider provider) throws IOException {
// Money nesnesini "100.50 TRY" formatında yaz
gen.writeString(String.format("%.2f %s", money.getAmount(), money.getCurrency()));
}
}
class MoneyDeserializer extends JsonDeserializer<Money> {
@Override
public Money deserialize(JsonParser parser,
DeserializationContext ctx) throws IOException {
String text = parser.getText(); // "100.50 TRY"
String[] parts = text.split(" ");
double amount = Double.parseDouble(parts[0]);
String currency = parts[1];
return new Money(currency, amount);
}
}Kullanım — Annotation ile
class Order {
private String id;
@JsonSerialize(using = MoneySerializer.class)
@JsonDeserialize(using = MoneyDeserializer.class)
private Money total;
// getter/setter...
}
// Serialize: {"id":"ORD-1","total":"250.00 TRY"}
// Deserialize: "250.00 TRY" → Money("TRY", 250.0)Kullanım — Module ile (Global)
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addSerializer(Money.class, new MoneySerializer());
module.addDeserializer(Money.class, new MoneyDeserializer());
mapper.registerModule(module);Module ile kayıt, tüm Money tiplerini otomatik olarak etkiler — her alana annotation eklemek gerekmez.
Gson Custom TypeAdapter
import com.google.gson.*;
import java.lang.reflect.Type;
class MoneyAdapter implements JsonSerializer<Money>, JsonDeserializer<Money> {
@Override
public JsonElement serialize(Money money, Type type,
JsonSerializationContext ctx) {
return new JsonPrimitive(
String.format("%.2f %s", money.getAmount(), money.getCurrency()));
}
@Override
public Money deserialize(JsonElement element, Type type,
JsonDeserializationContext ctx) {
String text = element.getAsString();
String[] parts = text.split(" ");
return new Money(parts[1], Double.parseDouble(parts[0]));
}
}
// Kayıt
Gson gson = new GsonBuilder()
.registerTypeAdapter(Money.class, new MoneyAdapter())
.create();9. DTO Pattern ve API Response Mapping
Gerçek dünya uygulamalarında veritabanı entity'leri ile API response'ları genellikle farklı yapıdadır. DTO (Data Transfer Object) pattern'i bu ayrımı sağlar.
Neden DTO Gerekli?
// Entity — veritabanı yapısını yansıtır
@Entity
class UserEntity {
@Id
private Long id;
private String name;
private String email;
private String passwordHash; // Bu asla API'da görünmemeli!
private LocalDateTime createdAt;
private boolean isDeleted; // Soft delete flag — API'da gereksiz
}Bu entity'yi doğrudan JSON'a çevirirseniz passwordHash ve isDeleted gibi alanlar da gider. @JsonIgnore ile engelleyebilirsiniz ama entity üzerinde API annotation'ları biriktirmek karmaşıklaşır.
DTO ile Temiz Ayrım
// API Response DTO — sadece dış dünyaya gidecek alanlar
class UserResponse {
private Long id;
private String name;
private String email;
@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
private LocalDateTime memberSince;
// Constructor
UserResponse(Long id, String name, String email, LocalDateTime memberSince) {
this.id = id;
this.name = name;
this.email = email;
this.memberSince = memberSince;
}
// getter'lar...
}
// API Request DTO — dışarıdan gelen veriyi karşılar
class CreateUserRequest {
@JsonProperty("name")
private String name;
@JsonProperty("email")
private String email;
@JsonProperty("password")
private String password;
// getter'lar...
}Mapper — Entity ↔ DTO Dönüşümü
class UserMapper {
static UserResponse toResponse(UserEntity entity) {
return new UserResponse(
entity.getId(),
entity.getName(),
entity.getEmail(),
entity.getCreatedAt()
);
}
static UserEntity toEntity(CreateUserRequest request) {
UserEntity entity = new UserEntity();
entity.setName(request.getName());
entity.setEmail(request.getEmail());
entity.setPasswordHash(hashPassword(request.getPassword()));
entity.setCreatedAt(LocalDateTime.now());
return entity;
}
private static String hashPassword(String password) {
// Gerçek uygulamada BCrypt kullanılır
return "hashed_" + password;
}
}Tam Akış Örneği
class UserController {
private final ObjectMapper mapper = new ObjectMapper();
// API'dan gelen JSON → DTO → Entity → Kaydet
void createUser(String requestJson) throws Exception {
// Deserialization
CreateUserRequest request = mapper.readValue(requestJson,
CreateUserRequest.class);
// DTO → Entity dönüşümü
UserEntity entity = UserMapper.toEntity(request);
// Veritabanına kaydet (simülasyon)
entity.setId(1L);
System.out.println("Kullanıcı kaydedildi: " + entity.getName());
// Entity → Response DTO → JSON
UserResponse response = UserMapper.toResponse(entity);
String responseJson = mapper.writeValueAsString(response);
System.out.println("API Response: " + responseJson);
}
}String requestJson = """
{
"name": "Zeynep Kaya",
"email": "zeynep@mail.com",
"password": "gizli123"
}
""";
new UserController().createUser(requestJson);
// Kullanıcı kaydedildi: Zeynep Kaya
// API Response: {"id":1,"name":"Zeynep Kaya","email":"zeynep@mail.com","memberSince":"23/02/2025 14:30"}Bu akışta dikkat edilmesi gerekenler:
passwordsadeceCreateUserRequestDTO'sunda var — response'a hiç geçmiyorpasswordHashsadece entity'de var — request DTO'sunda yokisDeletedhiçbir DTO'da yok — iç implementasyon detayıTarih formatı
UserResponseüzerinde@JsonFormatile kontrol ediliyor
API Wrapper Response
Çoğu API standart bir wrapper yapı kullanır:
class ApiResponse<T> {
private boolean success;
private String message;
private T data;
static <T> ApiResponse<T> ok(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.success = true;
response.message = "İşlem başarılı";
response.data = data;
return response;
}
static <T> ApiResponse<T> error(String message) {
ApiResponse<T> response = new ApiResponse<>();
response.success = false;
response.message = message;
response.data = null;
return response;
}
// getter'lar...
}// Kullanım
UserResponse user = new UserResponse(1L, "Ahmet", "ahmet@mail.com", LocalDateTime.now());
ApiResponse<UserResponse> response = ApiResponse.ok(user);
String json = mapper.writeValueAsString(response);
// {
// "success": true,
// "message": "İşlem başarılı",
// "data": {
// "id": 1,
// "name": "Ahmet",
// "email": "ahmet@mail.com",
// "memberSince": "23/02/2025 14:30"
// }
// }Deserialization:
ApiResponse<UserResponse> parsed = mapper.readValue(json,
new TypeReference<ApiResponse<UserResponse>>() {});
System.out.println(parsed.getData().getName()); // Ahmet⚠️ Dikkat: DTO'lar ve Entity'ler farklı katmanlardadır. Entity veritabanını yansıtır, DTO ise API kontratını. Bu ikisini asla aynı sınıfta birleştirmeyin — kısa vadede iş kurtarır gibi görünür ama uzun vadede bakım kabusu olur.
10. ObjectMapper Best Practices
ObjectMapper thread-safe'tir ve oluşturulması maliyetlidir. Her işlem için yeni bir ObjectMapper oluşturmak yerine tek bir instance paylaşılmalıdır.
// Yanlış — her çağrıda yeni ObjectMapper
class UserService {
String toJson(User user) throws Exception {
return new ObjectMapper().writeValueAsString(user); // Kötü!
}
}
// Doğru — tek instance, paylaşımlı
class UserService {
private static final ObjectMapper MAPPER = createMapper();
private static ObjectMapper createMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
return mapper;
}
String toJson(User user) throws Exception {
return MAPPER.writeValueAsString(user);
}
}💡 İpucu: Spring Boot'ta ObjectMapper zaten bir bean olarak tanımlıdır — @Autowired ile enjekte edebilirsiniz. application.properties üzerinden de yapılandırılabilir:
spring.jackson.serialization.indent-output=true
spring.jackson.deserialization.fail-on-unknown-properties=false
spring.jackson.default-property-inclusion=non-nullÖzet
Serialization nesneyi taşınabilir formata dönüştürmektir — modern Java'da bu format neredeyse her zaman JSON'dur;
java.io.Serializablekullanmaktan kaçının.Jackson Java'nın en yaygın JSON kütüphanesidir —
ObjectMapperilewriteValueAsString()(serialize) vereadValue()(deserialize) temel işlemlerdir.Jackson annotation'ları (
@JsonProperty,@JsonIgnore,@JsonFormat,@JsonCreator) serialization davranışını detaylı şekilde kontrol eder.TypeReference generic tiplerin (
List<User>,Map<String, Object>) doğru deserialize edilmesi için şarttır — type erasure sorununu çözer.Gson daha basit ve küçüktür — getter/setter gerektirmez, Android projelerinde yaygındır; Jackson ise Spring ekosisteminin standardıdır.
DTO pattern API katmanı ile veritabanı katmanını ayırır — güvenlik (hassas alanları gizleme) ve esneklik (API kontratını bağımsız değiştirme) sağlar.
AI Asistan
Sorularını yanıtlamaya hazır