التفاوض على المحتوى وجاكسون
التفاوض على المحتوى وجاكسون
في كل مرة يُعيد فيها مُتحكم REST في Spring Boot كائن Java، تحتاج إلى شيء يحوّله إلى بايتات عبر الشبكة. هذا الشيء هو جاكسون (Jackson) — مكتبة JSON التي تُهيّئها Spring Boot تلقائيًا فور إضافة spring-boot-starter-web. إن فهم كيفية قيام جاكسون بتسلسل كائنات Java وإلغاء تسلسلها، وكيفية توجيهه لتلبية احتياجات واجهة برمجية بعينها، مهارةٌ ستستخدمها في كل مشروع.
ما هو التفاوض على المحتوى؟
التفاوض على المحتوى هو آلية HTTP التي يتفق بموجبها العميل والخادم على تنسيق الاستجابة. يعبّر العميل عن تفضيله عبر ترويسة الطلب Accept (مثل Accept: application/json أو Accept: application/xml). يختار الخادم أفضل تنسيق يمكنه إنتاجه ويُعلنه في ترويسة الاستجابة Content-Type.
تجلس ContentNegotiationManager الخاصة بـ Spring MVC أمام كل دالة @RestController. تفحص ترويسة Accept، وتُعدّد حبّات HttpMessageConverter المسجلة في سياق التطبيق، وتختار المحوّل القادر على معالجة نوع الإعادة وإنتاج نوع الوسائط المطلوب. يتعامل محوّل جاكسون MappingJackson2HttpMessageConverter مع application/json (وapplication/*+json). إن لم يتطابق أي محوّل، يُعيد Spring 406 Not Acceptable.
spring-boot-starter-web فقط في مسار الفئات، يكون JSON هو التنسيق الوحيد المُهيَّأ تلقائيًا. تؤدي إضافة jackson-dataformat-xml إلى تفعيل التفاوض على XML بجانب JSON بشفافية تامة — دون أي إعداد إضافي.
كيف يُسلسل جاكسون كائن Java
تتجوّل ObjectMapper الخاصة بجاكسون عبر كائن Java بالتعكيس (أو عبر توليد كود بايت في الإصدارات الحديثة) وتُصدر JSON. افتراضيًا تتضمّن كل دالة getter عامة كحقل JSON، باستخدام اسم الخاصية المستنبط من اسم دالة الـ getter. فالدالة getUserName() تصبح "userName" في المخرج.
لنتأمل هذا الكيان:
يُنتج مُتحكم يُعيد Employee افتراضيًا:
لاحظ أن hireDate تظهر كمصفوفة. هذا هو السلوك الافتراضي لجاكسون مع java.time.LocalDate — وهو ما لا تريده في الغالب. الحل موضّح في الأسفل.
تعليقات جاكسون التي ستستخدمها يوميًا
@JsonProperty — تجاوز مفتاح JSON لحقل واحد:
@JsonIgnore — استبعاد حقل من التسلسل وإلغاء التسلسل معًا:
@JsonInclude — حذف الحقول ذات القيمة null (أو المجموعات الفارغة) من المخرج للإبقاء على الاستجابات خفيفة:
@JsonFormat — التحكم في طريقة تسلسل التواريخ والأرقام:
الآن تظهر hireDate كـ "2023-03-15" بدلًا من المصفوفة. هذا هو الحل الأكثر شيوعًا لمشاكل التواريخ.
@JsonNaming — تطبيق استراتيجية تسمية على جميع خصائص فئة دفعةً واحدة دون الحاجة لتعليق كل حقل على حدة:
إعداد جاكسون عالميًا عبر application.properties
يمكن تفعيل معظم إعدادات جاكسون أو إيقافها عبر فضاء الخاصية spring.jackson.* في Spring Boot، مما يُغني عن كتابة أي إعداد Java:
application.properties للإعدادات العالمية. تُخصَّص التجاوزات على مستوى التعليقات (@JsonFormat، @JsonProperty) للاستثناءات من تلك السياسة العالمية. هذا يُبقي فئات النطاق نظيفة ويُمركز استراتيجية التسلسل في مكان واحد سهل المراجعة والتغيير.
تسجيل حبّة ObjectMapper مخصصة
حين تحتاج إلى تحكم برمجي — تسجيل مُسلسِل مخصص، أو تفعيل ميزة لا تقابلها خاصية، أو إعداد JavaTimeModule يدويًا — أعلن عن حبّة ObjectMapper (أو Jackson2ObjectMapperBuilder):
ObjectMapper في السياق. تحلّ حبّتك محلّ الحبّة الافتراضية كليًا، لذا تأكد من إعادة تطبيق أي إعداد كنت تعتمده ضمنيًا (مثل تسجيل JavaTimeModule لدعم java.time).
كتابة مُسلسِل مخصص
أحيانًا تحتاج إلى منطق تسلسل لا يمكن لأي تعليق التعبير عنه — كتنسيق BigDecimal كسلسلة عملة، أو إخفاء جزء من حقل حساس. امتد من JsonSerializer<T>:
سجّله على الحقل:
التحكم في إلغاء التسلسل: التعامل مع الحقول المجهولة
افتراضيًا يرمي جاكسون UnrecognizedPropertyException حين يحتوي جسم JSON الوارد على حقل لا تُعلنه فئة DTO. هذا آمن لكنه هش — فهو يكسر واجهتك البرمجية في كل مرة يرسل فيها عميل حمولة أحدث. النهج الجاهز للإنتاج هو تجاهل الحقول المجهولة عالميًا:
أو على مستوى الفئة لـ DTO محددة:
CreateEmployeeRequest وEmployeeResponse) وطابق بينها. هذا يحمي من ثغرات الإسناد الجماعي ويتيح تطور مخططك باستقلالية عن سطح واجهتك البرمجية.
الخلاصة
تُوصّل Spring Boot جاكسون كمحرك JSON افتراضي لجميع مُتحكمات REST. تتحكم في مخرجاته على ثلاثة مستويات: عالميًا عبر خصائص spring.jackson.*، وعلى مستوى الفئة عبر تعليقات كـ @JsonNaming و@JsonInclude، وعلى مستوى الحقل عبر @JsonProperty و@JsonIgnore و@JsonFormat والمُسلسِلات المخصصة. استخدم دائمًا فئات DTO مخصصة بدلًا من تعريض كيانات JPA، وأعدّ write-dates-as-timestamps=false للحصول على تواريخ ISO مقروءة، وأوقف fail-on-unknown-properties لعقد واجهة برمجية أكثر مرونة. في الدرس القادم ستطبّق هذه المهارات على إصدار الواجهات البرمجية وأفضل ممارسات REST.