JSON إلى Java
توليد كلاسات Java POJO من JSON
إدخال JSON
مخرجات Java
ما هو تحويل JSON إلى كلاس Java؟
يأخذ تحويل JSON إلى كلاس Java كائن JSON خامًا وينتج تعريفات Plain Old Java Object (POJO) بحقول خاصة وgetters وsetters. لا يمتلك Java نظام أنواع JSON مدمجًا، لذا يحتاج كل رد من API أو ملف إعداد أو حمولة رسائل إلى كلاس مقابل قبل التعامل معه بطريقة آمنة الأنواع. تعتمد مكتبات مثل Jackson وGson على الانعكاس (reflection) لتعيين مفاتيح JSON إلى حقول Java، لكنها تتطلب وجود تعريفات الكلاسات أولًا.
يُعرّف POJO القياسي في Java المُستخدَم لتحليل JSON حقلًا خاصًا لكل مفتاح JSON، ومُنشئًا بدون معاملات، وزوجًا من getter/setter لكل حقل. تصبح كائنات JSON المتداخلة كلاسات منفصلة. تصبح المصفوفات حقول List<T> مع استيراد java.util.List. تتعيّن الأنواع الأساسية في JSON على أنواع Java الأساسية (int وdouble وboolean) أو أنواعها الغلافية (Integer وDouble وBoolean) عند استخدامها داخل الأنواع العامة. تتعيّن القيم الفارغة عادةً على Object أو نوع مرجعي قابل للإلغاء.
كتابة هذه التعريفات يدويًا عمل روتيني مضنٍ. عليك قراءة كل مفتاح JSON واستنتاج نوع Java من القيمة وتحويل أسماء المفاتيح من camelCase إلى حقول Java وإنشاء أسماء كلاسات بصيغة PascalCase للكائنات المتداخلة وإضافة كود getter/setter المتكرر. لكائن JSON بـ15 حقلًا و3 كائنات متداخلة، هذا يعني كتابة 4 كلاسات وأكثر من 30 تابعًا والحرص على الاتساق في كل شيء. يُنجز المحوّل هذا في أجزاء من الثانية.
لماذا تستخدم محوّل JSON إلى Java؟
إنشاء Java POJOs يدويًا من JSON يعني فحص كل حقل واستنتاج الأنواع من القيم النموذجية وكتابة أزواج getter/setter وتكرار العملية لكل كائن متداخل. عند تغيّر عقد الـAPI، تُحدّث كل شيء يدويًا. يُزيل المحوّل هذا العمل الميكانيكي.
حالات استخدام تحويل JSON إلى Java
جدول تعيين أنواع JSON إلى Java
كل قيمة JSON تتعيّن على نوع Java محدد. يوضح الجدول أدناه كيف يُترجم المحوّل كل نوع JSON إلى مكافئه في Java. يوضح عمود البديل الأنواع الغلافية المستخدمة في السياقات العامة مثل List<T>، أو ميزات Java الأحدث مثل Records.
| نوع JSON | مثال | نوع Java | البديل |
|---|---|---|---|
| string | "hello" | String | String |
| number (integer) | 42 | int | int / Integer |
| number (float) | 3.14 | double | double / Double |
| boolean | true | boolean | boolean / Boolean |
| null | null | Object | Object (or @Nullable String) |
| object | {"k": "v"} | NestedClass | Record (Java 16+) |
| array of strings | ["a", "b"] | List<String> | List<String> |
| array of objects | [{"id": 1}] | List<Item> | List<Item> |
| mixed array | [1, "a"] | List<Object> | List<Object> |
مرجع تعليقات JSON في Java
عند تحليل JSON باستخدام Jackson أو Gson، تتحكم التعليقات (annotations) في كيفية تعيين مفاتيح JSON إلى حقول Java، ومعالجة الحقول غير المعروفة، والتعامل مع القيم الفارغة. يغطي هذا المرجع التعليقات الأكثر شيوعًا عند العمل مع POJOs المولَّدة.
| التعليق | الغرض | المكتبة |
|---|---|---|
| @JsonProperty("name") | Maps a JSON key to a Java field with a different name | Jackson |
| @SerializedName("name") | Same as @JsonProperty, but for the Gson library | Gson |
| @JsonIgnoreProperties | Ignores unknown JSON keys during deserialization instead of failing | Jackson |
| @Nullable | Marks a field as accepting null values from JSON input | JSR 305 / JetBrains |
| @NotNull | Enforces that a field must not be null, throws on violation | Bean Validation |
| @JsonFormat(pattern=...) | Defines a date/time format for serialization and deserialization | Jackson |
POJO مقابل Record مقابل Lombok
تقدم Java ثلاث طرق شائعة لتعريف بنى موصوفة بالأنواع لحفظ بيانات JSON. كل طريقة تناسب أسلوب مشروع وإصدار Java مختلف. POJOs هي النمط التقليدي بأوسع توافق. Records تُقلل الكود المتكرر للبيانات غير القابلة للتغيير. Lombok يولّد getters وsetters ومُنشئات وقت الترجمة عبر التعليقات.
أمثلة برمجية
تُظهر هذه الأمثلة كيفية استخدام Java POJOs المولَّدة مع Jackson للتحليل، وكيفية توليد كلاسات Java برمجيًا من JavaScript وPython، وكيفية استخدام أداة سطر أوامر jsonschema2pojo للتوليد الدُفعي.
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private int id;
private String name;
private String email;
private boolean active;
private double score;
private Address address;
private List<String> tags;
// getters and setters omitted for brevity
public static void main(String[] args) throws Exception {
String json = "{\"id\":1,\"name\":\"Alice\",\"email\":\"alice@example.com\",\"active\":true,\"score\":98.5,\"address\":{\"street\":\"123 Main St\",\"city\":\"Springfield\",\"zip\":\"12345\"},\"tags\":[\"admin\",\"user\"]}";
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(json, User.class);
System.out.println(user.getName()); // -> Alice
}
}
class Address {
private String street;
private String city;
private String zip;
// getters and setters
}// Minimal JSON-to-Java-POJO generator in JS
function jsonToJava(obj, name = "Root") {
const classes = [];
function infer(val, fieldName) {
if (val === null) return "Object";
if (typeof val === "string") return "String";
if (typeof val === "number") return Number.isInteger(val) ? "int" : "double";
if (typeof val === "boolean") return "boolean";
if (Array.isArray(val)) {
const first = val.find(v => v !== null);
if (!first) return "List<Object>";
const elem = infer(first, fieldName);
const boxed = elem === "int" ? "Integer" : elem === "double" ? "Double" : elem === "boolean" ? "Boolean" : elem;
return `List<${boxed}>`;
}
if (typeof val === "object") {
const cls = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
build(val, cls);
return cls;
}
return "Object";
}
function build(obj, cls) {
const fields = Object.entries(obj).map(([k, v]) => {
const type = infer(v, k);
return ` private ${type} ${k};`;
});
classes.push(`public class ${cls} {\n${fields.join("\n")}\n}`);
}
build(obj, name);
return classes.join("\n\n");
}
console.log(jsonToJava({ id: 1, name: "Alice", scores: [98, 85] }, "User"));
// public class User {
// private int id;
// private String name;
// private List<Integer> scores;
// }import json
def json_to_java(obj: dict, class_name: str = "Root") -> str:
classes = []
def infer(val, name):
if val is None:
return "Object"
if isinstance(val, bool):
return "boolean"
if isinstance(val, int):
return "int"
if isinstance(val, float):
return "double"
if isinstance(val, str):
return "String"
if isinstance(val, list):
if not val:
return "List<Object>"
elem = infer(val[0], name)
boxed = {"int": "Integer", "double": "Double", "boolean": "Boolean"}.get(elem, elem)
return f"List<{boxed}>"
if isinstance(val, dict):
cls = name[0].upper() + name[1:]
build(val, cls)
return cls
return "Object"
def build(obj, cls):
fields = [f" private {infer(v, k)} {k};" for k, v in obj.items()]
classes.append(f"public class {cls} {{\n" + "\n".join(fields) + "\n}")
build(obj, class_name)
return "\n\n".join(classes)
data = json.loads('{"id": 1, "name": "Alice", "tags": ["admin"]}')
print(json_to_java(data, "User"))
# public class User {
# private int id;
# private String name;
# private List<String> tags;
# }# Install jsonschema2pojo (requires Java 8+)
# Download from https://github.com/joelittlejohn/jsonschema2pojo
# Generate POJOs from a JSON file
jsonschema2pojo --source user.json --target src/main/java \
--source-type json --annotation-style jackson2
# Generate with Gson annotations instead
jsonschema2pojo --source user.json --target src/main/java \
--source-type json --annotation-style gson
# From a JSON string via stdin
echo '{"id": 1, "name": "Alice", "tags": ["admin"]}' | \
jsonschema2pojo --source-type json --annotation-style jackson2 \
--target src/main/java --target-package com.example.model