การถอดรหัส JSON โดยใช้ json.Unmarshal vs json.NewDecoder.Decode


203

ฉันกำลังพัฒนาไคลเอนต์ API ที่ฉันต้องการเข้ารหัส JSON payload ตามคำขอและถอดรหัสเนื้อหาของ JSON จากการตอบสนอง

ฉันอ่านซอร์สโค้ดจากหลาย ๆ ไลบรารีและจากสิ่งที่ฉันเห็นฉันมีความเป็นไปได้สองอย่างในการเข้ารหัสและถอดรหัสสตริง JSON

ใช้json.Unmarshalผ่านสตริงการตอบสนองทั้งหมด

data, err := ioutil.ReadAll(resp.Body)
if err == nil && data != nil {
    err = json.Unmarshal(data, value)
}

หรือใช้ json.NewDecoder.Decode

err = json.NewDecoder(resp.Body).Decode(value)

ในกรณีของฉันเมื่อจัดการกับการตอบกลับ HTTP ที่ใช้งานio.Readerดูเหมือนว่ารุ่นที่สองจะต้องใช้รหัสน้อยลง แต่เนื่องจากฉันได้เห็นทั้งคู่ฉันสงสัยว่ามีการตั้งค่าใด ๆ ว่าฉันควรใช้โซลูชันมากกว่าที่อื่นหรือไม่

ยิ่งกว่านั้นคำตอบที่ได้รับการยอมรับจากคำถามนี้บอกว่า

โดยใช้แทนjson.Decoderjson.Unmarshal

แต่มันไม่ได้พูดถึงเหตุผล ฉันควรหลีกเลี่ยงการใช้งานจริงjson.Unmarshalหรือ


คำขอดึงนี้บน GitHubแทนที่การเรียก Unmarshal ด้วย json.NewDecoder เพื่อ "ลบบัฟเฟอร์ในการถอดรหัส JSON"
Matt

ขึ้นอยู่กับว่าคุณป้อนข้อมูลใดสะดวกกว่า blog.golang.org/json-and-goแสดงตัวอย่างการใช้เทคนิคทั้งสอง
rexposadas

15
IMO, ioutil.ReadAllเป็นเกือบเสมอสิ่งที่ผิดจะทำอย่างไร ไม่เกี่ยวข้องกับเป้าหมายของคุณ แต่ต้องการให้คุณมีหน่วยความจำต่อเนื่องเพียงพอที่จะจัดเก็บสิ่งที่อาจลงมาในท่อแม้ว่าการตอบสนอง 20TB ครั้งสุดท้ายจะเกิดขึ้นหลังจาก}JSON สุดท้ายของคุณ
ดัสติ

@Dustin คุณสามารถใช้io.LimitReaderเพื่อป้องกันไม่ให้
Inanc Gumus

คำตอบ:


240

มันขึ้นอยู่กับว่าข้อมูลของคุณคืออะไร หากคุณดูที่การประยุกต์ใช้Decodeเมธอดjson.Decoderจะบัฟเฟอร์ค่า JSON ทั้งหมดในหน่วยความจำก่อนที่จะทำการแยกค่าเป็นค่า Go ดังนั้นในกรณีส่วนใหญ่จะไม่มีประสิทธิภาพของหน่วยความจำอีกต่อไป (แม้ว่าจะสามารถเปลี่ยนแปลงได้อย่างง่ายดายในภาษารุ่นอนาคต)

ดังนั้นกฎข้อที่ดีกว่าคือ:

  • ใช้json.Decoderหากข้อมูลของคุณมาจากio.Readerสตรีมหรือคุณต้องถอดรหัสค่าหลายค่าจากสตรีมข้อมูล
  • ใช้json.Unmarshalหากคุณมีข้อมูล JSON อยู่ในหน่วยความจำแล้ว

สำหรับกรณีที่อ่านจากคำขอ HTTP ฉันจะเลือกjson.Decoderเนื่องจากคุณอ่านจากสตรีมอย่างเห็นได้ชัด


25
นอกจากนี้: โดยการตรวจสอบซอร์สโค้ด Go 1.3 เรายังสามารถเรียนรู้ว่าสำหรับการเข้ารหัสหากคุณใช้ json.Encoder มันจะใช้ซ้ำบัฟเฟอร์โลก (สนับสนุนโดย sync.Pool ใหม่) ซึ่งจะลดบัฟเฟอร์ที่สั่น หากคุณกำลังเข้ารหัส json จำนวนมาก json.Encoder แบ่งปันสระว่ายน้ำระดับโลกเพียงแห่งเดียวเท่านั้น เหตุผลที่ไม่สามารถทำได้สำหรับส่วนต่อประสาน json.Marshal นั้นเป็นเพราะไบต์จะถูกส่งกลับไปยังผู้ใช้และผู้ใช้ไม่มีวิธี "ส่งคืน" ไบต์ไปยังพูล ดังนั้นหากคุณทำการเข้ารหัสจำนวนมาก json.Marshal มักจะมีการสับบัฟเฟอร์เล็กน้อย
Aktau

@Flimzy: คุณแน่ใจเหรอ? รหัสที่มายังคงบอกว่ามันอ่านค่าทั้งหมดลงในบัฟเฟอร์ก่อนที่จะถอดรหัส: github.com/golang/go/blob/master/src/encoding/json/... Bufferedวิธีการอยู่ที่นั่นเพื่อให้คุณสามารถดูข้อมูลเพิ่มเติมใด ๆ ที่ได้รับการอ่านในบัฟเฟอร์ภายในหลังจากค่า
James Henstridge

@JamesHenstridge: ไม่คุณอาจพูดถูก ฉันแค่ตีความคำแถลงของคุณต่างไปจากที่ตั้งใจไว้ ขออภัยในความสับสน
Flimzy
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.