كيفية استخدام علامات الهيكل في Go

المقدمة

الهياكل، أو الهياكل المتعددة، تُستخدم لجمع عدة قطع من المعلومات معًا في وحدة واحدة. تُستخدم هذه المجموعات من المعلومات لوصف مفاهيم على مستوى أعلى، مثل العنوان المكون من الشارع، المدينة، الولاية، والرمز البريدي. عند قراءة هذه المعلومات من الأنظمة مثل قواعد البيانات أو واجهات برمجة التطبيقات، يمكنك استخدام علامات الهيكل للتحكم في كيفية تعيين هذه المعلومات إلى حقول الهيكل. تعتبر علامات الهيكل قطعًا صغيرة من البيانات المعرفية المرفقة بحقول هيكل توفر تعليمات للكود الآخر في Go الذي يعمل مع الهيكل.

كيف تبدو علامة الهيكل؟

علامات الهيكل في Go هي تعليقات تظهر بعد النوع في تعريف هيكل Go. تتألف كل علامة من سلاسل قصيرة مرتبطة بقيمة مقابلها.

A struct tag looks like this, with the tag offset with backtick ` characters:

type User struct {
	Name string `example:"name"`
}

يمكن للكود الآخر في Go بعد ذلك فحص هذه الهياكل واستخراج القيم المعينة للمفاتيح المحددة التي يطلبها. لا تؤثر علامات الهيكل على عمل الكود الخاص بك دون كود إضافي يفحصها.

جرّب هذا المثال لرؤية كيف تبدو علامات الهيكل، وأنه من دون كود من حزمة أخرى، فلن تكون لها أي تأثير.

package main

import "fmt"

type User struct {
	Name string `example:"name"`
}

func (u *User) String() string {
	return fmt.Sprintf("Hi! My name is %s", u.Name)
}

func main() {
	u := &User{
		Name: "Sammy",
	}

	fmt.Println(u)
}

سينتج ذلك:

Output
Hi! My name is Sammy

يعرّف هذا المثال نوعًا بالاسم User مع حقل Name. تم إعطاء الحقل Name علامة هيكلية بقيمة example:"name". سنشير إلى هذه العلامة الهيكلية المحددة في المحادثة بـ “علامة الهيكل المثالية” لأنها تستخدم الكلمة “example” كمفتاح لها. تحمل علامة الهيكل example القيمة "name" لحقل Name. على نوع User، نعرّف أيضًا طريقة String() المطلوبة من واجهة fmt.Stringer. سيتم استدعاء هذه الطريقة تلقائيًا عند تمرير النوع إلى fmt.Println وتمنحنا فرصة لإنتاج نسخة مهيأة بشكل جيد من الهيكل الخاص بنا.

في جسم الدالة main، ننشئ مثالًا جديدًا من نوعنا User ونمرره إلى fmt.Println. على الرغم من وجود علامة هيكلية في الهيكل، نرى أنه ليس لها تأثير على عمل هذا الكود Go. سيتصرف بالضبط بنفس الطريقة إذا كانت العلامة الهيكلية غير موجودة.

لاستخدام علامات الهيكل لتحقيق شيء ما، يجب كتابة كود Go آخر لفحص الهياكل في وقت التشغيل. تحتوي حزمة المكتبة القياسية على حزم تستخدم علامات الهيكل كجزء من عملها. الأكثر شيوعًا من هذه هي حزمة encoding/json.

ترميز JSON

الترميز النصي لكائنات JavaScript (JSON) هو تنسيق نصي لترميز مجموعات من البيانات المنظمة تحت مفاتيح سلسلة مختلفة. يُستخدم عادة لتبادل البيانات بين برامج مختلفة نظرًا لأن التنسيق بسيط بما يكفي حتى توجد مكتبات لفك تشفيره في العديد من اللغات المختلفة. يأتي ما يلي كمثال على JSON:

{
  "language": "Go",
  "mascot": "Gopher"
}

يحتوي هذا الكائن JSON على مفتاحين، language و mascot. بعد هذه المفاتيح تتبع القيم المرتبطة. هنا، يحمل مفتاح language قيمة Go ويتم تعيين mascot قيمة Gopher.

يستخدم مُشفر JSON في مكتبة القياسية علامات البنية كتوضيحات تُشير إلى المُشفر كيف تود تسمية حقولك في الإخراج JSON. يمكن العثور على هذه الآليات لترميز وفك تشفير JSON في encoding/json package.

جرب هذا المثال لرؤية كيفية ترميز JSON بدون علامات البنية:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

type User struct {
	Name          string
	Password      string
	PreferredFish []string
	CreatedAt     time.Time
}

func main() {
	u := &User{
		Name:      "Sammy the Shark",
		Password:  "fisharegreat",
		CreatedAt: time.Now(),
	}

	out, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

ستظهر الناتج التالي:

Output
{ "Name": "Sammy the Shark", "Password": "fisharegreat", "CreatedAt": "2019-09-23T15:50:01.203059-04:00" }

وصفنا هيكلًا يصف مستخدمًا بحقول تشمل اسمهم وكلمة مرورهم ووقت إنشاء المستخدم. ضمن دالة main، ننشئ مثالًا لهذا المستخدم عن طريق توفير قيم لجميع الحقول ما عدا PreferredFish (سامي يحب جميع أنواع الأسماك). ثم قمنا بتمرير مثيل User إلى دالة json.MarshalIndent. يتم استخدام هذا لكي نرى الناتج بتنسيق JSON بسهولة أكبر دون استخدام أداة تنسيق خارجية. يمكن استبدال هذا الاستدعاء بـ json.Marshal(u) لطباعة JSON بدون أي فراغات إضافية. يتحكم الوسيطتان الإضافيتان في دالة json.MarshalIndent في البادئة إلى الإخراج (التي قمنا بتجاهلها باستخدام السلسلة الفارغة)، والأحرف المستخدمة للتبويب، وهنا تكون فارغة. يتم تسجيل أي أخطاء تنتج من json.MarshalIndent ويتم إنهاء البرنامج باستخدام os.Exit(1). وأخيرًا، قمنا بتحويل []byte المُرجعة من json.MarshalIndent إلى string وقمنا بتمرير السلسلة الناتجة إلى fmt.Println للطباعة على الطرفية.

تظهر حقول الهيكل بالضبط كما هي مسماة. هذا ليس النمط النموذجي للـ JSON الذي قد تتوقعه، على الرغم من ذلك، والذي يستخدم تسمية الجمال لأسماء حقول. ستقوم بتغيير أسماء الحقول لتتبع النمط الجملي في المثال التالي. كما سترى عند تشغيل هذا المثال، هذا لن يعمل لأن أسماء الحقول المرغوبة تتعارض مع قواعد Go حول أسماء الحقول المصدرة.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

type User struct {
	name          string
	password      string
	preferredFish []string
	createdAt     time.Time
}

func main() {
	u := &User{
		name:      "Sammy the Shark",
		password:  "fisharegreat",
		createdAt: time.Now(),
	}

	out, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

سيظهر الناتج التالي:

Output
{}

في هذا الإصدار، قمنا بتغيير أسماء الحقول لتكون بتنسيق الجملة. الآن Name أصبحت name، Password أصبحت password، وأخيرًا CreatedAt أصبحت createdAt. داخل جسم main، قمنا بتغيير إنشاء هيكلنا لاستخدام هذه الأسماء الجديدة. ثم نمرر الهيكل إلى وظيفة json.MarshalIndent كما فعلنا من قبل. الناتج هذه المرة هو كائن JSON فارغ، {}.

تقوم الجمل بتسمية الحقول بشكل صحيح يتطلب أن يكون الحرف الأول منخفض الحال. على الرغم من أن JSON لا يهتم كيفية تسمية الحقول، إلا أن Go يهتم، حيث يُشير ذلك إلى رؤية الحقل خارج الحزمة. نظرًا لأن حزمة encoding/json هي حزمة منفصلة عن حزمة main التي نستخدمها، يجب أن نجعل الحرف الأول حروف كبيرة لجعله مرئيًا لـ encoding/json. يبدو أننا في مأزق. نحتاج إلى طريقة لتوجيه المُرمِّز JSON بما نرغب أن يسمى به هذا الحقل.

استخدام العلامات الهيكلية للتحكم في الترميز

يمكنك تعديل المثال السابق ليحتوي على حقول مصدرة مُشفرة بشكل صحيح باستخدام أسماء الحقول المركبة بأسلوب الإبلاغ عن كل حقل باستخدام علامة هيكلية. تعرف علامة الهيكل التي يعترف بها encoding/json على مفتاح json وقيمة تتحكم في الناتج. من خلال وضع النسخة المركبة بأسلوب الجملة لأسماء الحقول كقيمة لمفتاح json، سيستخدم المُشفر تلك الاسم بدلاً من ذلك. يُصحح هذا المثال المحاولتين السابقتين:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

type User struct {
	Name          string    `json:"name"`
	Password      string    `json:"password"`
	PreferredFish []string  `json:"preferredFish"`
	CreatedAt     time.Time `json:"createdAt"`
}

func main() {
	u := &User{
		Name:      "Sammy the Shark",
		Password:  "fisharegreat",
		CreatedAt: time.Now(),
	}

	out, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

هذا سينتج:

Output
{ "name": "Sammy the Shark", "password": "fisharegreat", "preferredFish": null, "createdAt": "2019-09-23T18:16:17.57739-04:00" }

لقد قمنا بتغيير حقول الهيكل لتصبح مرئية لحزم البرامج الأخرى من خلال تحويل أول حروف أسمائهم إلى حروف كبيرة. ومع ذلك، في هذه المرة قمنا بإضافة علامات هيكلية في شكل json:"name"، حيث كانت "name" هي الاسم الذي أردنا أن يستخدمه json.MarshalIndent عند طباعة هيكلنا كـ JSON.

لقد قمنا الآن بتهيئة JSON بنجاح بشكل صحيح. لاحظ، ومع ذلك، أن الحقول لبعض القيم تم طباعتها حتى لو لم نقم بتعيين تلك القيم. يمكن لمشفر JSON التخلص من هذه الحقول أيضًا، إذا كنت ترغب في ذلك.

إزالة الحقول الفارغة في JSON

من الشائع كتم إخراج الحقول التي لم يتم تعيينها في JSON. نظرًا لأن جميع أنواع البيانات في Go لها “قيمة صفرية”، أي قيمة افتراضية يتم تعيينها لها، يحتاج الحزمة encoding/json إلى معلومات إضافية لتمكينها من التعرف على أن بعض الحقول يجب أن تعتبر غير معينة عندما تفترض هذه القيمة الصفرية. يمكنك إضافة ,omitempty إلى الجزء القيمي لأي علامة هيكل json لتخبر محول JSON بكتم إخراج هذا الحقل عندما تكون قيمة الحقل مساوية للقيمة الصفرية. يُظهر المثال التالي كيفية تصحيح الأمثلة السابقة لعدم إخراج الحقول الفارغة بعد الآن:

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

type User struct {
	Name          string    `json:"name"`
	Password      string    `json:"password"`
	PreferredFish []string  `json:"preferredFish,omitempty"`
	CreatedAt     time.Time `json:"createdAt"`
}

func main() {
	u := &User{
		Name:      "Sammy the Shark",
		Password:  "fisharegreat",
		CreatedAt: time.Now(),
	}

	out, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

سيُظهر هذا المثال:

Output
{ "name": "Sammy the Shark", "password": "fisharegreat", "createdAt": "2019-09-23T18:21:53.863846-04:00" }

لقد قمنا بتعديل الأمثلة السابقة بحيث يحتوي الحقل PreferredFish الآن على علامة هيكل json:"preferredFish,omitempty". تسبب وجود التعديل ,omitempty في تخطي المحول JSON لهذا الحقل، نظرًا لأننا قررنا تركه غير معين. كانت قيمته null في إخراج أمثلتنا السابقة.

تبدو هذه النتيجة أفضل بكثير، لكننا لا زلنا نقوم بطباعة كلمة مرور المستخدم. توفر حزمة encoding/json طريقة أخرى لتجاهل الحقول الخاصة تمامًا.

تجاهل الحقول الخاصة

يجب تصدير بعض الحقول من الهياكل بحيث يمكن للحزم الأخرى التفاعل بشكل صحيح مع النوع. ومع ذلك، قد تكون طبيعة هذه الحقول حساسة، لذا في هذه الظروف، نود أن يتجاهل مُشفر JSON الحقل بالكامل — حتى عندما يتم تعيينه. يتم ذلك باستخدام القيمة الخاصة - كوسيطة القيمة إلى علامة الهيكل json:.

هذا المثال يُصلح مشكلة عرض كلمة مرور المستخدم.

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
	"time"
)

type User struct {
	Name      string    `json:"name"`
	Password  string    `json:"-"`
	CreatedAt time.Time `json:"createdAt"`
}

func main() {
	u := &User{
		Name:      "Sammy the Shark",
		Password:  "fisharegreat",
		CreatedAt: time.Now(),
	}

	out, err := json.MarshalIndent(u, "", "  ")
	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	fmt.Println(string(out))
}

عند تشغيل هذا المثال، سترى هذا الإخراج:

Output
{ "name": "Sammy the Shark", "createdAt": "2019-09-23T16:08:21.124481-04:00" }

الشيء الوحيد الذي قمنا بتغييره في هذا المثال عن الأمثلة السابقة هو أن حقل كلمة المرور الآن يستخدم القيمة الخاصة "-" لعلامة هيكل json:. في الإخراج من هذا المثال أن حقل password لم يعد موجودًا.

هذه الميزات في حزمة encoding/json,omitempty، "-"، وخيارات أخرى — ليست معايير. ما تقرره الحزمة بشأن قيم علامة الهيكل يعتمد على تنفيذها. لأن حزمة encoding/json جزء من المكتبة القياسية، فقد قامت الحزم الأخرى أيضًا بتنفيذ هذه الميزات بنفس الطريقة كمسألة اعتياد. ومع ذلك، من المهم قراءة الوثائق لأي حزمة طرف ثالث تستخدم علامات الهيكل لتعرف ما إذا كانت مدعومة وما إذا كانت غير مدعومة.

الاستنتاج

الوسوم الهيكلية توفر وسيلة قوية لتعزيز وظائف الشفرة التي تعمل مع الهياكل الخاصة بك. تقدم العديد من مكتبات المكتبة القياسية والطرف الثالث طرقًا لتخصيص عملها من خلال استخدام الوسوم الهيكلية. استخدامها بفعالية في الشفرة الخاصة بك يوفر هذا السلوك المخصص ويوثق بإيجاز كيفية استخدام هذه الحقول للمطورين المستقبليين.

Source:
https://www.digitalocean.com/community/tutorials/how-to-use-struct-tags-in-go