היום למדתי: לא הופכים OpenStruct ל JSON
בכל מערכת יש את הרגע הזה שמימוש וממשק מתנגשים, מה שנקרא אבסטרקציה דולפת. אחד המקומות שזה קורה ב Ruby הוא המימוש של Open Struct.
הסיפור בקצרה - ברובי בדרך כלל הגישה למקום ב Hash נעשית עם סימן סוגריים מרובעים לדוגמה:
3.1.1 :001 > d = {foo: 10, bar: 20}
=> {:foo=>10, :bar=>20}
3.1.1 :002 > d[:foo]
=> 10
3.1.1 :003 >
אבל מתכנתים שבאים מ JavaScript רגילים לגשת ל Hash באמצעות אופרטור הנקודה. ברובי נקודה היא קריאה למתודה ו OpenStruct זו מעטפת סביב Hash שיוצרת מתודה עבור כל שדה מידע ב Hash. זה נראה ככה:
3.1.1 :005 > require 'ostruct'
=> true
3.1.1 :007 > dd = OpenStruct.new(d)
=> #<OpenStruct foo=10, bar=20>
3.1.1 :008 > dd.foo
=> 10
3.1.1 :009 > dd.bar
=> 20
עד לפה הכל טוב ואפשר גם להפעיל to_json
ולקבל את כל השדות בחזרה בתור JSON:
3.1.1 :002 > require 'ostruct'
=> true
3.1.1 :003 > require 'json'
=> true
3.1.1 :004 > OpenStruct.new({ foo: 10, bar: 20 })
=> #<OpenStruct foo=10, bar=20>
3.1.1 :005 > OpenStruct.new({ foo: 10, bar: 20 }).to_json
=> "\"#<OpenStruct foo=10, bar=20>\""
אבל כשמעבירים את אותו קוד לריילס הקידוד ל JSON משתנה ומקבלים את התוצאה המפתיעה הבאה:
3.1.1 :001 > OpenStruct.new({ foo: 10, bar: 20 }).to_json
=> "{\"table\":{\"foo\":10,\"bar\":20}}"
מאיפה הגיע לשם ה table
???
חיפוש ב Stack Overflow הביא אותי לתשובה הזאת. מסתבר שבמימוש של OpenStruct כל הפריטים נשמרים בתור ערכים של משתנה מחלקה בשם table
, והקידוד ל JSON של ריילס פשוט מקודד את כל משתני המחלקה.
וכמובן אחרי שגילינו את האבסטרקציה הדולפת הפיתרון הוא מאוד פשוט:
3.1.1 :077 > OpenStruct.new({a: 10, b: 20}).to_h.to_json
=> "{\"a\":10,\"b\":20}"
מאחר ויש כבר פונקציה to_h
שמחזירה את כל המידע בתור Hash רגיל, צריך רק לקרוא לה ולתרגם את ה Hash שהיא מחזירה ל JSON כמו שריילס כבר יודע לעשות.