首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 實體類與 JSON 互轉最佳實務:Jackson 註解驅動與型別安全

     編輯:Java 相關知識

前言

JSON 是現代 Web 服務的標準資料交換格式。Java 中將 POJO(實體類)與 JSON 互轉,Jackson 是事實標準庫(Spring Boot 預設整合)。本文展示如何以註解驅動實現零配置、型別安全、高效能的序列化/反序列化,並解決常見的日期、巢狀物件、忽略欄位、多態等實戰問題。

Maven 依賴

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version> <!-- 檢查最新版 -->
</dependency>
<!-- 模組:JDK 8 日期、JSR310、Kotlin、Scala 等 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.17.0</version>
</dependency>

核心工具類:JsonUtils(單例 ObjectMapper)

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;

public final class JsonUtils {
    private static final ObjectMapper MAPPER = newObjectMapper();

    private static ObjectMapper newObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        // 註冊 JDK 8 日期時間模組
        mapper.registerModule(new JavaTimeModule());
        // 禁用將日期序列化為 timestamp(長整數),改用 ISO-8601 字串
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        // 忽略未知屬性(反序列化時 JSON 含類別無對應欄位不報錯)
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        // 允許單引號、非引號欄位名(寬鬆模式,視安全性啟用)
        // mapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
        // mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
        return mapper;
    }

    private JsonUtils() {}

    /** 物件 → JSON 字串 */
    public static String toJson(Object obj) {
        try { return MAPPER.writeValueAsString(obj); }
        catch (JsonProcessingException e) { throw new RuntimeException("JSON 序列化失敗", e); }
    }

    /** JSON 字串 → 物件(型別安全) */
    public static  T fromJson(String json, Class clazz) {
        try { return MAPPER.readValue(json, clazz); }
        catch (IOException e) { throw new RuntimeException("JSON 反序列化失敗", e); }
    }

    /** JSON 字串 → 複雜泛型型別(如 List) */
    public static  T fromJson(String json, JavaType javaType) {
        try { return MAPPER.readValue(json, javaType); }
        catch (IOException e) { throw new RuntimeException("JSON 反序列化失敗", e); }
    }

    /** 建構泛型 JavaType 輔助方法 */
    public static JavaType getCollectionType(Class collectionClass, Class... elementClasses) {
        return MAPPER.getTypeFactory().constructParametricType(collectionClass, elementClasses);
    }

    /** 格式化輸出(開發除錯用) */
    public static String toPrettyJson(Object obj) {
        try { return MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj); }
        catch (JsonProcessingException e) { throw new RuntimeException("JSON 格式化失敗", e); }
    }
}

實體類註解完整範例

import com.fasterxml.jackson.annotation.*;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import java.time.LocalDateTime;
import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true) // 類別層級忽略未知屬性
@JsonInclude(JsonInclude.Include.NON_NULL)  // 全域忽略 null 欄位(可被欄位註解覆蓋)
public class User {

    @JsonProperty("user_id")           // 指定 JSON 欄位名
    private Long id;

    @JsonProperty("user_name")
    private String username;

    @JsonIgnore                        // 完全忽略:不序列化、不反序列化
    private String passwordHash;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // 僅反序列化(請求入參),不輸出
    private String rawPassword;

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)  // 僅序列化(回傳),不接收
    private String displayName;

    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @JsonDeserialize(using = LocalDateTimeDeserializer.class)
    @JsonSerialize(using = LocalDateTimeSerializer.class)
    private LocalDateTime createdAt;

    @JsonProperty("roles")
    private List roleCodes;

    @JsonProperty("profile")
    private UserProfile profile; // 巢狀物件,自動遞迴轉換

    // Getter/Setter 省略(Lombok @Data 可自動生成)
    // 必須有無參建構子供 Jackson 實例化
}

常見場景處理

場景註解/配置說明
欄位名映射 (camelCase ↔ snake_case)@JsonProperty("snake_case") 或全域 mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE)建議全域統一策略,減少欄位級註解
忽略 null 輸出類別 @JsonInclude(Include.NON_NULL) 或欄位 @JsonInclude(Include.NON_EMPTY)NON_EMPTY 同時忽略空集合/空字串
日期時間格式化@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone="GMT+8") + JSR310 模組支援 LocalDate/LocalDateTime/Instant/ZonedDateTime
枚舉序列化為名稱而非 ordinal枚舉類別加 @JsonFormat(shape=JsonFormat.Shape.STRING) 或 @JsonValue 在 getter 上避免數值序列化導致前後端不一致
多態反序列化(同一介面多實作)@JsonTypeInfo(use=Id.NAME, property="type") + @JsonSubTypesJSON 需包含 type 欄位標識實際類別
循環引用(雙向關聯)@JsonManagedReference (父) + @JsonBackReference (子) 或 @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")IdentityInfo 適用於任意深度循環
動態忽略欄位(視圖)@JsonView(Views.Public.class) 在欄位/方法上 + mapper.writerWithView(Views.Public.class)同一實體不同 API 回傳不同欄位集

使用範例

public class JsonDemo {
    public static void main(String[] args) {
        User user = new User();
        user.setId(1001L);
        user.setUsername("john_doe");
        user.setRawPassword("secret123"); // 僅寫入
        user.setCreatedAt(LocalDateTime.now());
        user.setRoleCodes(List.of("ADMIN", "USER"));
        user.setProfile(new UserProfile("John", "Doe", "john@example.com"));

        // 1. 物件 → JSON
        String json = JsonUtils.toJson(user);
        System.out.println("JSON:\n" + JsonUtils.toPrettyJson(user));
        // 輸出包含:user_id, user_name, displayName, createdAt, roles, profile
        // 不包含:passwordHash, rawPassword

        // 2. JSON → 物件
        String input = "{\"user_id\":1002,\"user_name\":\"jane\",\"raw_password\":\"pwd\",\"created_at\":\"2024-01-15 10:30:00\"}";
        User parsed = JsonUtils.fromJson(input, User.class);
        System.out.println("Parsed username: " + parsed.getUsername()); // jane
        System.out.println("Raw password (write-only): " + parsed.getRawPassword()); // pwd
        // displayName 為 null(READ_ONLY)

        // 3. 複雜泛型反序列化
        String listJson = "[{\"user_id\":1,\"user_name\":\"a\"},{\"user_id\":2,\"user_name\":\"b\"}]";
        JavaType type = JsonUtils.getCollectionType(List.class, User.class);
        List users = JsonUtils.fromJson(listJson, type);
        System.out.println("List size: " + users.size()); // 2
    }
}

效能優化建議

  • 重用 ObjectMapper:單例模式,建構成本高(註冊模組、掃描註解)。
  • 啟用 Afterburner:mapper.enable(MapperFeature.USE_GETTERS_AS_SETTERS) 等,或依賴 jackson-module-afterburner 產生 bytecode 存取器,提升 20-30% 速度。
  • 避免大物件 pretty print:生產環境僅用精簡輸出。
  • 流式 API:超大 JSON(>10MB)用 JsonParser/JsonGenerator 串流處理,避免全量載入記憶體。

結語

Jackson 以豐富的註解體系、強大的型別系統與卓越效能,成為 Java JSON 處理的首選。掌握核心註解(@JsonProperty、@JsonIgnore、@JsonFormat、@JsonView、@JsonTypeInfo)與 ObjectMapper 關鍵配置,即可應對 95% 以上的序列化場景。建議專案統一封裝 JsonUtils,集中管理命名策略、日期格式、忽略策略,確保前後端協議一致且易於維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved