JSON با Jackson، (بخش سوم) Tree Model
نوشتهی حاضر به Tree Model میپردازد که یکی از سه روش تولید و خواندن JSON با بستهی نرمافزاری Jackson است. همانند مقالهی قبلی مثالی را با این روش بررسی کرده و در انتها به این سوال میپردازیم: «در چه مواردی این متد را بر دو رقیب دیگر ترجیح دهیم؟»
در مقالهی اول پس از معرفی بستهی نرمافزاری Jackson یک مثال با روش Data Binding بررسی کردیم و در مقالهی دوم روش Stream Method معرفی شد، سومین روش یا Tree Model در ادامه شرح داده میشود.
تولید JSON در Tree Model
روش کار در Tree Model به این صورت است که ابتدا یک شی از ObjectNode به عنوان «شی ریشه» یا «root object» ساخته میشود. شی ریشه معادل کلاس اصلی در JSON است. در ادامه مقادیر key/value را به ریشه ملحق میکنیم. برای این کار از نسخههای مختلف متد put یا set از کلاس ObjectNode استفاده خواهیم کرد. برای حاصل کردن objectNode دو روش وجود دارد:
- روش اول: ساخت یک شی ObjectMapper و استفاده از متد createObjectNode آن.
com.fasterxml.jackson.databind.node.ObjectNode createObjectNode()
- روش دوم: ساخت یک شی JsonNodeFactory و استفاده از متد objectNode آن.
com.fasterxml.jackson.databind.node.ObjectNode objectNode()
در تکه کد زیر از روش دوم استفاده شده است.
private static JsonNode createRootJsonNode() { //create root node JsonNodeFactory jsonNodeFactory = new JsonNodeFactory(false); //false for bigDecimalExact ObjectNode rootObjectNode = jsonNodeFactory.objectNode(); Employee emp = JacksonDemo0.createEmployee(); //id rootObjectNode.put("id", emp.getId()); //names rootObjectNode.put("firstName", emp.getFirstName()); rootObjectNode.put("lastName", emp.getLastName()); //phone numbers ArrayNode phoneNumbersarrayNode = jsonNodeFactory.arrayNode(); for (String phoneNumber : emp.getPhoneNumbers()) { phoneNumbersarrayNode.add(phoneNumber); } rootObjectNode.set("phoneNumbers", phoneNumbersarrayNode); //address ObjectNode addressObjectNode = jsonNodeFactory.objectNode(); addressObjectNode.put("city", emp.getAddress().getCity()); addressObjectNode.put("street", emp.getAddress().getStreet()); addressObjectNode.put("zipCode", emp.getAddress().getZipCode()); rootObjectNode.set("address", addressObjectNode); return rootObjectNode; }
هنگام ساخت شیءریشه، با استفاده از متدهای زیر key/Value های JSON را ساخته و به آن اضافه کردیم. value میتواند مقادیر ساده (رشته یا عدد) یا مقادیر مرکب (مانند شی JSON یا آرایه) باشد. از لیست زیر دو متد set و setAll برای مواردی است که نوع value یکی از موارد مرکب باشد. در این صورت ابتدا شیءای از ObjectNode یا ArrayNode را ساخته و سپس روی شیء ریشه، الصاق میکنیم.
ObjectNode put(String fieldName, !!VariableType!! v) JsonNode set(String fieldName, JsonNode value) JsonNode setAll(Map<String,? extends JsonNode> properties)
VariableType در شبه کد فوق میتواند یکی از موارد زیر باشد:
BigDecimal, BigInteger, boolean, Boolean, byte[], double, float, int, long, short, String
شکل زیر ساختار کلاسهای API و نسبت JsonNode را با ObjectNode و ArrayNode نشان میدهد.
تکه کد زیر نشان میدهد پس از ساختن «شیءریشه»، نوشتن متن JSON ای تولید شده در یک فایل چگونه انجام میشود.
private static void emp2JSON_TreeModel() throws IOException, JsonProcessingException { JsonNode rootJsonNode = createRootJsonNode(); JsonFactory jsonFactory = new JsonFactory(); JsonGenerator jsonGenerator = jsonFactory.createGenerator(new File("treeMethodDB.json"), JsonEncoding.UTF8); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.writeTree(jsonGenerator, rootJsonNode); }
از یکی از متدهای زیر، که از کلاس ObjectMapper هستند، استفاده کردهایم:
void writeTree(JsonGenerator jgen, JsonNode rootNode) void writeTree(JsonGenerator jgen, TreeNode rootNode)
اجازه دهید از تفاوتهای دو متد عبور کنیم و تنها به این نکته اشاره داشته باشیم که TreeNode interface توسط JsonNode پیادهسازی شده و تفاوت فاحشی بین دو متد بالا نیست.
JsonGenerator نیز، کلاس آشنایی است. چرا که در مقالهی قبل برای تولید JSON با Stream Method از آن استفاده کردیم. به وسیلهی این کلاس مشخص میکنیم دادهای که تولید میکنیم «کجا» و با چه «Encoding»ای نوشته شود.
خواندن JSON در Tree Model
همانطور که در تولید JSON به روش Tree Model بخش زیادی از کار مربوط به ساختن «شیء ریشه» بود، در «خواندن» به این روش نیز، تمرکز کار روی بهدست آوردن یک شیء ریشه از نوع JsonNode و سپس پیمایش (traverse) آن است.
برای به دست آوردن root JsonNode دو روش وجود دارد:
- روش اول: استفاده از یکی از نسخه های overload شدهی متد زیر از کلاس ObjectMapper که میتواند دادهی JSON را از یکی از منابع بخواند و یک شیء از نوع JsonNode برگرداند.
JsonNode readTree(!!SourceType!! source)
SourceType در شبه کد فوق میتواند یکی از موارد زیر باشد:
byte[] , File, InputStream, JsonParser, Reader, String, URL
- روش دوم: استفاده از یکی از نسخههای overload شدهی متد زیر از کلاس ObjectMapper که پارامتر اول آن، نوع منبع را معرفی کرده و پارامتر دوم از نوع Class است. با تنظیم پارامتر دوم به JsonNode.class خروجی مطلوب حاصل میشود.
T readValue(!!SourceType!! src, Class<T> valueType)
SourceType در شبه کد فوق میتواند یکی از موارد زیر باشد:
byte[] , File, InputStream, JsonParser, Reader, String, URL AND DataInput
در مثال زیر با استفاده از روش اول یک root JsonNode را از یک فایل به دست میآوریم:
private static JsonNode retrieveRootJsonNode() throws JsonProcessingException, IOException { ObjectMapper objectMapper = new ObjectMapper(); JsonNode rootJsonNode = objectMapper.readTree(new File("treeMethodDB.json")); return rootJsonNode; }
حال باید بتوانیم به فیلدهای شیءریشه دسترسی پیدا کنیم. متدهای زیر در JsonNode به این منظور ارائه شدهاند.
توضیح | متد |
برای آرایهها، المان موجود در index را برمیگرداند. شروع شمارش از صفر است. اگر المان با شمارهی index در دسترس نباشد یکی از موارد زیر اتفاق میافتد:
|
JsonNode get(int index) |
برای Object ها مقدار فیلدی که نام آن property باشد را بر میگرداند. اگر :
|
JsonNode get(String property) |
مشابه متدهای get در ردیفهای فوق، با این تفاوت که به جای null جاوا در موارد گفته شده، شیءای از نوع MissingNode برمیگرداند. | JsonNode path(int index) JsonNode path(String property) |
|
JsonNode with(String propertyName) |
|
JsonNode withArray(String propertyName) |
پس از اینکه فیلد را در قالب JsonNode به دست آوردیم، میتوانیم از متدهای زیر استفاده کنیم تا مقدار value آن را مشاهده کنیم.
- متدهای سری XXXvalue : (مانند intValue) این متدها برای استخراج مقدار به کار میروند، در مواردی که نوع آن را به طور دقیق میدانیم.
- متدهای سری asXXX: (مانند asInt) این متدها برای تبدیل و استخراج مقدار به کار میروند. صرف نظر از نوع ذخیرهی داده در JSON آن را به نوع دلخواه ما تغییر میدهد (اگر تغییر ممکن باشد، مانند تبدیل قابل قبول int به String).
در مثال زیر از متدهایی که توضیح داده شد، برای استخراج داده از JSON و ساختن یک Employee استفاده شده است.
private static Employee json2Employee_TreeModel() throws JsonProcessingException, IOException { Employee emp = new Employee(); JsonNode rootJsonNode = retrieveRootJsonNode(); //id emp.setId(rootJsonNode.get("id").intValue()); //names emp.setFirstName(rootJsonNode.get("firstName").textValue()); emp.setLastName(rootJsonNode.get("lastName").textValue()); //phoneNumbers ArrayList<String> phoneNumbers = new ArrayList<>(); for (JsonNode phoneNumberNode : rootJsonNode.get("phoneNumbers")) { phoneNumbers.add(phoneNumberNode.textValue()); } emp.setPhoneNumbers(phoneNumbers); //address Address address = new Address(); address.setCity(rootJsonNode.get("address").get("city").textValue()); address.setStreet(rootJsonNode.get("address").get("street").textValue()); address.setZipCode(rootJsonNode.get("address").get("zipCode").intValue()); emp.setAddress(address); return emp; }
پیمایش JSON در Tree Model
در مثال فوق بخش مهمی از متدهای Tree Model را برای به دست آوردن فیلدهای JSON دیدیم. استفاده از تمام متدهای فوق مستلزم دانستن نام فیلد داده هستند. اما اگر یک دادهی JSON ای در اختیار ما قرار گیرد و در مورد ساختار و فیلدهای آن هیچ اطلاعی نداشته باشیم چگونه باید آن را پیمایش کرده و اطلاعات مورد نیاز را استخراج کنیم؟
Tree Model برای چنین مواردی متدهایی را در اختیار قرار داده است. از جمله:
Iterator<String> fieldNames() Iterator<Map.Entry<String,JsonNode>> fields() Iterator<JsonNode> elements() Iterator<JsonNode> iterator() abstract JsonNodeType getNodeType() boolean isXXX()
به عنوان مثال اگر شکل JSON ای که در قسمتهای قبلی برای یک شی از کلاس Employee ساختهایم را دریافت کرده و شیءریشهی آن را به عنوان ورودی به متد زیر بدهیم میتوانیم فیلدهای آن را پیمایش کرده و در مورد هر فیلد نام و نوع value را به دست آورده و چاپ کنیم.
private static void printNameOfFields(JsonNode root) { Iterator<String> fNameIterator = root.fieldNames(); while (fNameIterator.hasNext()) { System.out.println(fieldName+": "+root.get(fieldName).getNodeType()); } }
خروجی اجرای تکه کد فوق به این صورت است:
id: NUMBER firstName: STRING lastName: STRING phoneNumbers: ARRAY address: OBJECT
ویژگی Tree Model
عمدهی استفاده این روش زمانی است که بین کلاسهای پروژه یک POJO معادل JSON نداریم، چرا که اگر یک کلاس معادل JSON وجود داشته باشد بهترین و به صرفهترین متد استخراج داده از آن، استفاده از Data Binding است. در مواردی که دادهای در اختیارمان قرارگرفته و در مورد محتوا، نام فیلدها، نوع Value فیلدها و… هیچ اطلاعی نداریم، متدهای پیمایش Tree Model راهگشا خواهند بود.
این روش، آشناترین شکل از داده برای برنامهنویسانی است که با فرمت XML کار کردهاند، چرا که در سطح مفهومی، ساختار درختیِ اشیاء JSON بسیار مشابه مفهوم DOM در فضای XML است.
.
.
.
آدرس کانال تلگرام: JavaCupIR@
آدرس اکانت توییتر: JavaCupIR@
آدرس صفحه اینستاگرام: javacup.ir
آدرس گروه لینکدین: Iranian Java Developers