في الآونة الأخيرة ، حدث أن قمت بتطوير عميل http على Go لخدمة توفر REST API مع json كتنسيق ترميز. مهمة قياسية ، ولكن أثناء العمل كان علي مواجهة مشكلة غير قياسية. أقول لك ما هي النقطة.كما تعلم ، يحتوي تنسيق json على أنواع بيانات. أربعة بدائية: سلسلة ، عدد ، منطقية ، خالية ؛ ونوعان هيكليان: كائن وصفيف. في هذه الحالة ، نحن مهتمون بالأنواع البدائية. فيما يلي مثال لرمز json مع أربعة حقول من أنواع مختلفة:{
"name":"qwerty",
"price":258.25,
"active":true,
"description":null,
}
كما يوضح المثال ، يتم إحاطة قيمة السلسلة بعلامات اقتباس. رقمي - لا يحتوي على علامات اقتباس. يمكن أن يحتوي النوع المنطقي على قيمة واحدة فقط من قيمتين: صواب أو خطأ (بدون علامات اقتباس). وبالتالي يكون النوع الفارغ فارغًا (أيضًا بدون علامات اقتباس).والآن المشكلة نفسها. في مرحلة ما ، في فحص مفصل لرمز json الذي تم تلقيه من خدمة طرف ثالث ، وجدت أن أحد الحقول (دعنا نسميه سعرًا) يحتوي بشكل دوري على قيمة سلسلة (الرقم في علامات الاقتباس) بالإضافة إلى القيمة العددية. بمعنى ، يمكن أن يُرجع نفس الاستعلام بمعلمات مختلفة رقمًا كرقم ، أو يمكنه إرجاع نفس الرقم كسلسلة. لا يمكنني أن أتخيل كيف يتم تنظيم الشفرة التي تُرجع مثل هذه النتائج في الطرف الآخر ، ولكن يبدو أن هذا يرجع إلى حقيقة أن الخدمة نفسها هي مُجمِّع وتسحب البيانات من مصادر مختلفة ، ولم يقدم المطورون استجابة الخادم json إلى تنسيق واحد. ومع ذلك ، من الضروري العمل مع ما هو.ولكن بعد ذلك فوجئت أكثر. الحقل المنطقي (دعنا نسميه نشطًا) ، بالإضافة إلى صواب وخطأ ، أعاد قيم السلسلة "صواب" و "خطأ" وحتى رقمي 1 و 0 (صواب وخطأ ، على التوالي).كل هذا الارتباك حول أنواع البيانات لن يكون حرجًا إذا كنت سأعالج json say في PHP المكتوب بشكل ضعيف ، ولكن Go لديه كتابة قوية ، ويتطلب إشارة واضحة إلى نوع الحقل الذي تم إلغاء تسلسله. ونتيجة لذلك ، كانت هناك حاجة لتطبيق آلية تسمح بتحويل جميع قيم الحقل النشط إلى نوع منطقي أثناء عملية إلغاء التسلسل ، وأي قيمة لحقل السعر إلى قيمة رقمية.لنبدأ بمجال السعر.لنفترض أن لدينا كود json مثل هذا:[
{"id":1,"price":2.58},
{"id":2,"price":7.15}
]
أي أن json يحتوي على مصفوفة من الكائنات ذات حقلين من النوع الرقمي. يبدو رمز إلغاء التسلسل القياسي لهذا json on Go كما يلي:type Target struct {
Id int `json:"id"`
Price float64 `json:"price"`
}
func main() {
jsonString := `[{"id":1,"price":2.58},
{"id":4,"price":7.15}]`
targets := []Target{}
err := json.Unmarshal([]byte(jsonString), &targets)
if err != nil {
fmt.Println(err)
return
}
for _, t := range targets {
fmt.Println(t.Id, "-", t.Price)
}
}
في هذا الكود ، سنقوم بإلغاء تسلسل حقل المعرف إلى int وحقل السعر إلى float64. لنفترض الآن أن رمز json الخاص بنا يبدو كما يلي:[
{"id":1,"price":2.58},
{"id":2,"price":"2.58"},
{"id":3,"price":7.15},
{"id":4,"price":"7.15"}
]
أي أن حقل السعر يحتوي على قيم لكل من النوع الرقمي والسلسلة. في هذه الحالة ، يمكن فقط فك القيم الرقمية لحقل السعر إلى النوع float64 ، بينما تتسبب قيم السلسلة في حدوث خطأ حول عدم توافق الأنواع. هذا يعني أنه لا يوجد float64 ولا أي نوع بدائي آخر مناسب لإلغاء تسلسل هذا المجال ، ونحن بحاجة إلى نوع مخصص خاص بنا مع منطق إلغاء التسلسل الخاص به.على هذا النحو ، قم بتعريف بنية CustomFloat64 بحقل Float64 واحد من النوع float64.type CustomFloat64 struct{
Float64 float64
}
ووضح على الفور هذا النوع لحقل السعر في الهيكل المستهدف:type Target struct {
Id int `json:"id"`
Price CustomFloat64 `json:"price"`
}
تحتاج الآن إلى وصف منطقك الخاص لفك ترميز حقل من نوع CustomFloat64.تحتوي حزمة "التشفير / json" على طريقتين خاصتين: MarshalJSON و UnmarshalJSON ، المصممة لتخصيص منطق التشفير وفك التشفير لنوع بيانات مستخدم معين. يكفي تجاوز هذه الأساليب ووصف التنفيذ الخاص بك.تجاوز أسلوب UnmarshalJSON لنوع عشوائي CustomFloat64. في هذه الحالة ، من الضروري اتباع توقيع الطريقة بدقة ، وإلا فلن تعمل ببساطة ، والأهم من ذلك أنها لن تنتج خطأ.func (cf *CustomFloat64) UnmarshalJSON(data []byte) error {
عند الإدخال ، تأخذ هذه الطريقة شريحة من البايت (البيانات) ، والتي تحتوي على قيمة حقل معين من json الذي تم فك ترميزه. إذا قمنا بتحويل هذا التسلسل من وحدات البايت إلى سلسلة ، فسوف نرى قيمة الحقل بالضبط في الشكل الذي كُتبت فيه في json. بمعنى ، إذا كان نوع سلسلة ، فسوف نرى بالضبط سلسلة بعلامات اقتباس مزدوجة ("258") ، إذا كانت من نوع رقمي ، فسوف نرى سلسلة بدون علامات اقتباس (258).لتمييز قيمة رقمية من قيمة سلسلة ، يجب عليك التحقق مما إذا كان الحرف الأول هو علامة اقتباس. نظرًا لأن حرف الاقتباس المزدوج في جدول UNICODE يستهلك بايتًا واحدًا ، نحتاج فقط إلى التحقق من البايت الأول لشريحة البيانات من خلال مقارنته برقم الحرف في UNICODE. هذا هو الرقم 34. لاحظ أنه بشكل عام ، الحرف لا يعادل البايت ، حيث يمكن أن يستغرق أكثر من بايت واحد. الرمز في Go يكافئ رون (رون). في حالتنا ، هذا الشرط كافٍ:if data[0] == 34 {
إذا تم استيفاء الشرط ، فإن القيمة لها نوع سلسلة ، ونحتاج إلى الحصول على السلسلة بين علامات الاقتباس ، أي شريحة الشريحة بين البايت الأول والأخير. تحتوي هذه الشريحة على قيمة عددية يمكن فك شفرتها في النوع البدائي float64. هذا يعني أنه يمكننا تطبيق طريقة json.Unmarshal عليها ، مع حفظ النتيجة في حقل Float64 لهيكل CustomFloat64.err := json.Unmarshal(data[1:len(data)-1], &cf.Float64)
إذا لم تبدأ شريحة البيانات بعلامة اقتباس ، فإنها تحتوي بالفعل على نوع بيانات رقمي ، ويمكننا تطبيق طريقة json.Unmarshal مباشرة على شريحة البيانات بالكامل.err := json.Unmarshal(data, &cf.Float64)
إليك الشفرة الكاملة لطريقة UnmarshalJSON:func (cf *CustomFloat64) UnmarshalJSON(data []byte) error {
if data[0] == 34 {
err := json.Unmarshal(data[1:len(data)-1], &cf.Float64)
if err != nil {
return errors.New("CustomFloat64: UnmarshalJSON: " + err.Error())
}
} else {
err := json.Unmarshal(data, &cf.Float64)
if err != nil {
return errors.New("CustomFloat64: UnmarshalJSON: " + err.Error())
}
}
return nil
}
ونتيجة لذلك ، باستخدام طريقة json.Unmarshal إلى كود json الخاص بنا ، سيتم تحويل جميع قيم حقل السعر بشفافية إلى نوع بدائي float64 بالنسبة لنا ، وستتم كتابة النتيجة إلى حقل Float64 من بنية CustomFloat64.قد نحتاج الآن إلى تحويل بنية Target إلى json. ولكن ، إذا طبقنا طريقة json.Marshal مباشرة على نوع CustomFloat64 ، فإننا نقوم بتسلسل هذه البنية ككائن. نحن بحاجة إلى ترميز حقل السعر في قيمة عددية. لتخصيص منطق الترميز من النوع المخصص CustomFloat64 ، نقوم بتطبيق طريقة MarshalJSON لذلك ، مع مراقبة توقيع الأسلوب بدقة:func (cf CustomFloat64) MarshalJSON() ([]byte, error) {
json, err := json.Marshal(cf.Float64)
return json, err
}
كل ما عليك القيام به في هذه الطريقة هو مرة أخرى لاستخدام طريقة json.Marshal ، ولكن تطبيقها بالفعل ليس على هيكل CustomFloat64 ، ولكن على حقل Float64 الخاص به. من الطريقة نعيد شريحة البايت المستقبلة والخطأ.إليك الشفرة الكاملة التي تعرض نتائج التسلسل وإلغاء التسلسل (يتم حذف تدقيق الأخطاء للإيجاز ، ويكون عدد البايت برمز علامة الاقتباس المزدوجة ثابتًا):package main
import (
"encoding/json"
"errors"
"fmt"
)
type CustomFloat64 struct {
Float64 float64
}
const QUOTES_BYTE = 34
func (cf *CustomFloat64) UnmarshalJSON(data []byte) error {
if data[0] == QUOTES_BYTE {
err := json.Unmarshal(data[1:len(data)-1], &cf.Float64)
if err != nil {
return errors.New("CustomFloat64: UnmarshalJSON: " + err.Error())
}
} else {
err := json.Unmarshal(data, &cf.Float64)
if err != nil {
return errors.New("CustomFloat64: UnmarshalJSON: " + err.Error())
}
}
return nil
}
func (cf CustomFloat64) MarshalJSON() ([]byte, error) {
json, err := json.Marshal(cf.Float64)
return json, err
}
type Target struct {
Id int `json:"id"`
Price CustomFloat64 `json:"price"`
}
func main() {
jsonString := `[{"id":1,"price":2.58},
{"id":2,"price":"2.58"},
{"id":3,"price":7.15},
{"id":4,"price":"7.15"}]`
targets := []Target{}
_ := json.Unmarshal([]byte(jsonString), &targets)
for _, t := range targets {
fmt.Println(t.Id, "-", t.Price.Float64)
}
jsonStringNew, _ := json.Marshal(targets)
fmt.Println(string(jsonStringNew))
}
نتيجة تنفيذ التعليمات البرمجية:1 - 2.58
2 - 2.58
3 - 7.15
4 - 7.15
[{"id":1,"price":2.58},{"id":2,"price":2.58},{"id":3,"price":7.15},{"id":4,"price":7.15}]
دعنا ننتقل إلى الجزء الثاني وننفذ نفس الرمز لإلغاء التسلسل json مع قيم غير متناسقة للحقل المنطقي.لنفترض أن لدينا كود json مثل هذا:[
{"id":1,"active":true},
{"id":2,"active":"true"},
{"id":3,"active":"1"},
{"id":4,"active":1},
{"id":5,"active":false},
{"id":6,"active":"false"},
{"id":7,"active":"0"},
{"id":8,"active":0},
{"id":9,"active":""}
]
في هذه الحالة ، يشير الحقل النشط إلى نوع منطقي ووجود قيمة واحدة فقط من قيمتين: صواب وخطأ. يجب تحويل القيم غير المنطقية إلى قيمة منطقية أثناء إزالة التسلسل.في المثال الحالي ، نعترف بالمباريات التالية. تتوافق القيم الحقيقية مع: true (منطقي) ، true (سلسلة) ، 1 (سلسلة) ، 1 (رقمية). القيمة الخاطئة تتوافق مع: false (منطقي) ، false (سلسلة) ، 0 (سلسلة) ، 0 (رقمية) ، "" (سلسلة فارغة).أولاً ، سنعلن عن البنية المستهدفة لإزالة التسلسل. كنوع الحقل النشط ، نحدد على الفور النوع المخصص CustomBool:type Target struct {
Id int `json:"id"`
Active CustomBool `json:"active"`
}
CustomBool هي بنية بها حقل منطقي واحد من نوع bool:type CustomBool struct {
Bool bool
}
نقوم بتطبيق طريقة UnmarshalJSON لهذه البنية. سأعطيك الشفرة على الفور:func (cb *CustomBool) UnmarshalJSON(data []byte) error {
switch string(data) {
case `"true"`, `true`, `"1"`, `1`:
cb.Bool = true
return nil
case `"false"`, `false`, `"0"`, `0`, `""`:
cb.Bool = false
return nil
default:
return errors.New("CustomBool: parsing \"" + string(data) + "\": unknown value")
}
}
نظرًا لأن الحقل النشط في حالتنا يحتوي على عدد محدود من القيم ، يمكننا اتخاذ قرار باستخدام بنية حالة التبديل حول قيمة حقل Bool في بنية CustomBool. للتحقق ، تحتاج فقط إلى كتلتين من الحالات. في المربع الأول ، نتحقق من القيمة "true" ، في الثانية - false.عند تسجيل القيم المحتملة ، يجب الانتباه إلى دور الحصى (هذه علامة اقتباس على المفتاح بالحرف E في تخطيط اللغة الإنجليزية). تسمح لك هذه الشخصية بالهروب من علامات الاقتباس المزدوجة في سلسلة. للتوضيح ، قمت بتأطير القيم بعلامات اقتباس وبدون علامات اقتباس مع هذا الرمز. وهكذا ، `false` يتوافق مع السلسلة false (بدون علامات الاقتباس ، اكتب bool في json) ، و` false 'يتوافق مع السلسلة "false" (مع علامات الاقتباس ، اكتب سلسلة في json). نفس الشيء مع قيم "1" و "1" `الأول هو الرقم 1 (مكتوب في json بدون علامات اقتباس) ، والثاني هو السلسلة" 1 "(في json المكتوبة بعلامات اقتباس). هذا الإدخال "" هو سلسلة فارغة ، أي في شكل json يبدو كالتالي: "".تتم كتابة القيمة المطابقة (صواب أو خطأ) مباشرةً في الحقل Bool في بنية CustomBool:cb.Bool = true
في الكتلة الافتراضية ، نعرض خطأ يفيد بأن الحقل له قيمة غير معروفة:return errors.New("CustomBool: parsing \"" + string(data) + "\": unknown value")
الآن يمكننا تطبيق طريقة json.Unmarshal على كود json الخاص بنا ، وسيتم تحويل قيم الحقل النشط إلى نوع بدائي بدائي.نقوم بتطبيق طريقة MarshalJSON لبنية CustomBool:func (cb CustomBool) MarshalJSON() ([]byte, error) {
json, err := json.Marshal(cb.Bool)
return json, err
}
لا جديد هنا. الأسلوب تسلسل حقل Bool بنية CustomBool.إليك الشفرة الكاملة التي تعرض نتائج التسلسل وإلغاء التسلسل (تم حذف تدقيق الأخطاء للإيجاز):package main
import (
"encoding/json"
"errors"
"fmt"
)
type CustomBool struct {
Bool bool
}
func (cb *CustomBool) UnmarshalJSON(data []byte) error {
switch string(data) {
case `"true"`, `true`, `"1"`, `1`:
cb.Bool = true
return nil
case `"false"`, `false`, `"0"`, `0`, `""`:
cb.Bool = false
return nil
default:
return errors.New("CustomBool: parsing \"" + string(data) + "\": unknown value")
}
}
func (cb CustomBool) MarshalJSON() ([]byte, error) {
json, err := json.Marshal(cb.Bool)
return json, err
}
type Target struct {
Id int `json:"id"`
Active CustomBool `json:"active"`
}
func main() {
jsonString := `[{"id":1,"active":true},
{"id":2,"active":"true"},
{"id":3,"active":"1"},
{"id":4,"active":1},
{"id":5,"active":false},
{"id":6,"active":"false"},
{"id":7,"active":"0"},
{"id":8,"active":0},
{"id":9,"active":""}]`
targets := []Target{}
_ = json.Unmarshal([]byte(jsonString), &targets)
for _, t := range targets {
fmt.Println(t.Id, "-", t.Active.Bool)
}
jsonStringNew, _ := json.Marshal(targets)
fmt.Println(string(jsonStringNew))
}
نتيجة تنفيذ التعليمات البرمجية:1 - true
2 - true
3 - true
4 - true
5 - false
6 - false
7 - false
8 - false
9 - false
[{"id":1,"active":true},{"id":2,"active":true},{"id":3,"active":true},{"id":4,"active":true},{"id":5,"active":false},{"id":6,"active":false},{"id":7,"active":false},{"id":8,"active":false},{"id":9,"active":false}]
الموجودات
أولا. يسمح لك تجاوز طرق MarshalJSON و UnmarshalJSON لأنواع البيانات العشوائية بتخصيص التسلسل وإلغاء تسلسل حقل كود json محدد. بالإضافة إلى حالات الاستخدام المشار إليها ، يتم استخدام هذه الوظائف للعمل مع الحقول الخالية.ثانيا. تنسيق ترميز json text هو أداة مستخدمة على نطاق واسع لتبادل المعلومات ، وإحدى مزاياها على التنسيقات الأخرى هي توافر أنواع البيانات. يجب مراقبة الامتثال لهذه الأنواع بدقة.