8 คะแนน โดย GN⁺ 2025-09-22 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • Sj.h เป็น ตัวพาร์ส JSON แบบเบามาก ที่มีขนาดเพียงประมาณ 150 บรรทัด และใช้งานได้ในสภาพแวดล้อมที่รองรับ C99
  • มีจุดเด่นอย่าง ไม่จัดสรรหน่วยความจำเลย และ เก็บสถานะให้น้อยที่สุด จึงเหมาะกับสภาพแวดล้อมแบบ embedded ที่ต้องการความเบาและการพึ่งพาภายนอกให้น้อยที่สุด
  • ใช้วิธีให้ผู้ใช้จัดการ การพาร์สตัวเลขและสตริง เองโดยตรง จึงสามารถออกแบบการทำงานส่วนที่เกี่ยวข้องได้อย่างอิสระ
  • รองรับ ข้อความผิดพลาดแบบบรรทัด:คอลัมน์ ทำให้ ดีบัก ได้อ่านง่ายและระบุตำแหน่งปัญหาได้ชัดเจน
  • เป็นซอฟต์แวร์สาธารณสมบัติที่ทุกคนสามารถ ใช้งาน แก้ไข และเผยแพร่ต่อได้อย่างอิสระ

แนะนำโปรเจกต์

  • Sj.h คือไฟล์เฮดเดอร์ C99 ที่ให้ความสามารถในการพาร์ส JSON ด้วย โค้ดเท่าที่จำเป็น โดยไม่มีฟีเจอร์ส่วนเกินหรือการจัดสรรหน่วยความจำที่ไม่จำเป็น
  • การติดตั้งใช้งานทั้งหมดมีขนาดเพียงประมาณ 150 บรรทัด จึงเหมาะกับระบบ embedded, เครื่องมือขนาดเล็ก หรือกรณีที่ต้องการลดการพึ่งพาไลบรารีภายนอก

คุณสมบัติหลัก

  • ทำงานแบบ zero-allocations และ minimal state จึงมีภาระด้านหน่วยความจำน้อยมาก
  • มี ข้อความผิดพลาด ที่มาพร้อมข้อมูล บรรทัด:คอลัมน์ ทำให้ตรวจหาตำแหน่งปัญหาระหว่างการพาร์สได้ง่าย
  • ไม่มีตัวพาร์สตัวเลขมาให้โดยตรง ผู้ใช้จึงสามารถเลือกใช้วิธีที่ต้องการเอง เช่น strtod, atoi
  • การพาร์สสตริงก็สามารถทำเองได้โดยตรง หรือจะสร้างการรองรับอย่าง Unicode surrogate pair ตามความต้องการก็ได้
  • ซอร์สโค้ดทั้งหมดเผยแพร่แบบสาธารณสมบัติ (Unlicense) จึงสามารถใช้งานและแก้ไขได้โดยไม่มีข้อจำกัดด้านไลเซนส์

ตัวอย่างการใช้งาน

  • มีตัวอย่างอย่างง่ายสำหรับพาร์สสตริง JSON ไปเป็นโครงสร้าง Rect
    char *json_text = "{ \"x\": 10, \"y\": 20, \"w\": 30, \"h\": 40 }";  
    typedef struct { int x, y, w, h; } Rect;  
    ...  
    
  • ใช้ API ที่เรียบง่ายและตรงไปตรงมา เช่น sj_Reader, sj_Value, sj_reader, sj_read, sj_iter_object
  • การพาร์สค่าตัวเลข หรือ การเปรียบเทียบคีย์-ค่า ต้องเขียนเอง (ไม่มี builtin ให้)
  • สามารถดูตัวอย่างการใช้งานเพิ่มเติมได้ในโฟลเดอร์ demo

ไลเซนส์

  • Sj.h เป็น ซอฟต์แวร์สาธารณสมบัติ ที่ทุกคนสามารถใช้งานได้โดยไม่มีข้อจำกัด
  • รายละเอียดเพิ่มเติมดูได้ในไฟล์ LICENSE

อื่น ๆ

  • โค้ดและโครงสร้างโฟลเดอร์เรียบง่าย จึงนำไปใช้ได้ทันทีโดยไม่ต้องมีขั้นตอนตั้งค่าหรือบิลด์เพิ่มเติม
  • เป็นไลบรารีแบบอิสระ ไม่มีการพึ่งพาภายนอก และเหมาะกับสภาพแวดล้อมที่ต้องการทั้ง ความเบา และ ความสะดวกในการใช้งาน

1 ความคิดเห็น

 
GN⁺ 2025-09-22
ความเห็นจาก Hacker News
  • เหตุผลที่ฉันชอบงานของผู้เขียนคนนี้คือส่วนใหญ่เป็นไลบรารีไฟล์เดียวที่เขียนด้วย ANSI C หรือ Lua โดยโฟกัสที่ขอบเขตงานชัดเจน มีอินเทอร์เฟซที่ใช้งานง่ายและเอกสารยอดเยี่ยม อีกทั้งไลเซนส์ซอฟต์แวร์เสรีก็ถูกใจฉันด้วย นอกจากโปรเจ็กต์นี้ยังมีงานอื่นที่ฉันชอบอีกหลายตัว: log.c(ไลบรารี logging C99 แบบเรียบง่าย), microui(ไลบรารี immediate-mode UI ขนาดเล็กมาก), fe(ภาษาขนาดเล็กที่ฝังใช้งานได้), microtar(ไลบรารี tar แบบ lightweight), cembed(เครื่องมือฝังไฟล์ลงใน C header), ini(ไลบรารีขนาดเบาสำหรับไฟล์ตั้งค่า .ini), json.lua(ไลบรารี JSON แบบ lightweight สำหรับ Lua), lite(โปรแกรมแก้ไขข้อความขนาดเบาที่ทำด้วย Lua), cmixer(ตัวผสมเสียงแบบ portable สำหรับเกม), uuid4(ไลบรารี uuid4 ขนาดเล็กที่เขียนด้วย C)
    • ฉันหยิบ log.c มาใช้กับโปรเจ็กต์ C แทบทุกครั้ง ไม่รู้มาก่อนเลยว่าผู้เขียนทำไลบรารีไว้เยอะขนาดนี้ log.c ใช้ง่ายมาก แนะนำได้จริง
    • ตอนทำเกมด้วย LOVE2D ฉันเคยใช้ไลบรารี Lume และเคยเจอผู้เขียนใน IRC chatroom อยู่สองสามครั้ง ครั้งหนึ่งฉันเคยบอกว่าไอเดียของเขาไม่ดี แต่พอกลับมาดูอีกทีกลายเป็นว่าเป็นไอเดียที่ดี น่าลองดู https://github.com/rxi/lume
    • เป็นโอเพนซอร์สก็จริง แต่ไม่ใช่ free software แบบแท้ ๆ
  • ไลบรารีนี้ไม่ได้ตรวจ signed integer overflow ในบรรทัดต่อไปนี้: L89, L149, L150 จึงอาจเกิด undefined behavior ได้กับอินพุตบางค่า
    • นักพัฒนาบางคนชอบวัฒนธรรมของไลบรารี C แบบ single-header ที่เรียบง่าย ตัวอย่างเช่นสตรีมเมอร์อย่าง Tsoding พวกเขารู้ดีว่าไลบรารีแบบนี้ไม่ได้โฟกัสเรื่องความปลอดภัยหรือฟีเจอร์ครบถ้วน ไม่ใช่ทุกโปรเจ็กต์จะเป็นซอฟต์แวร์ระดับธุรกิจที่ต้องเจอกับลูกค้าจำนวนมหาศาล ดังนั้นแนวทางแบบนี้ก็พอรับได้
    • มีบทความที่พูดถึงปัญหาความเทอะทะของไลบรารีที่พยายามรองรับ edge case ทุกอย่างได้ดี และมีกระทู้ที่เกี่ยวข้องด้วย ถ้าพยายามจัดการทุก error ความซับซ้อนจะพุ่งขึ้นมาก บางครั้งมันก็ไม่ใช่หน้าที่ของไลบรารีด้วยซ้ำ
    • UB อาจเกิดขึ้นได้ก็ต่อเมื่อความลึกของเลเวลเกิน 2 พันล้าน หรือในกรณีที่สองคือมีจำนวนบรรทัดเกิน 2 พันล้าน ถ้าจำกัดอินพุต JS ไว้ที่ 1GB ปัญหาที่ใหญ่กว่านี้น่าจะโผล่จากส่วนอื่นของสแตกก่อน ถ้าต้องประมวลผล JSON ที่เกิน 2GB ฉันก็คงเปลี่ยน int ในซอร์สเป็น 64 บิตทั้งหมดอยู่แล้ว แต่ต่อให้ทำแบบนั้น ถ้าอินพุตเกิน 2^64 ก็พังอยู่ดี ฉันคงไม่คิดจะใส่เช็ก int overflow ทีละจุดในโค้ด
    • เพราะ int เป็น 32 บิต ปัญหาจะเกิดก็ต่อเมื่อมีค่าซ้อนลึก 2 พันล้านชั้นในแต่ละบรรทัด, ไฟล์ที่มีมากกว่า 2 พันล้านบรรทัด, หรือบรรทัดเดียวที่ยาวเกิน 2 พันล้านอักขระ
    • ฉันว่าคงใช้ไลบรารีนี้ใน production ไม่ได้
  • ไลบรารีนี้ยืดหยุ่นค่อนข้างมาก จะบอกว่าวิธีนี้ไม่ดีก็คงไม่ได้ แต่คนที่จะหยิบไปใช้โดยไม่อ่านโค้ดควรระวัง และนี่ก็คือเหตุผลหลักที่ทำให้โค้ดมันเล็กขนาดนี้ จากเดโมตัวอย่างใน README มันทำงานแบบนี้<br><code>{"x",10eee"y"22:5,{[:::,,}]"w"7"h"33<br>rect: { 10, 22, 7, 33 }</code>
    • โดยทั่วไป parser แบบนี้ตั้งสมมติฐานว่าอินพุตถูกต้องอยู่แล้ว ส่วนงาน validation เป็นปัญหาอีกเรื่องหนึ่งที่ไลบรารีนี้ไม่ได้ครอบคลุม สุดท้ายมันก็มีหน้าที่แค่ดึงข้อมูลออกมา
    • งั้นแปลว่ามันผิดเหรอ?
  • ฉันคิดว่าไลบรารี parser JSON โดยรวมเป็นหลุมดำแห่งความทุกข์ เพราะแต่ละตัวถูกออกแบบมาใช้กับงานคนละแบบ และเต็มไปด้วย abstraction ซับซ้อน บ่อยครั้งก็เป็นทั้งสองอย่างพร้อมกัน จริง ๆ แล้วถ้าทำเฉพาะส่วนที่ตรงกับจุดประสงค์ของตัวเอง ปัญหานี้ก็ไม่ได้ยากมากนัก
    • น่าทึ่งที่ไลบรารี JSON สมัยใหม่ซับซ้อนกันได้ขนาดนี้ ไลบรารี JSON แบบ single-header สำหรับ C++ ของ nlohmann ที่เคยเรียบง่ายมากนั้น<br>* มีอายุ 13 ปีแล้ว<br>* ทุกวันนี้ก็ยังมี pull request ถูก merge อย่างต่อเนื่อง<br>* มี unit test ถึง 122 ล้านรายการ<br>ถึงอย่างนั้นพวกเขาเองก็ยอมรับว่าไม่ใช่ตัวที่เร็วที่สุด ถ้าต้องการความเร็วจริง ๆ ก็แนะนำ simdjson อย่าคิดทำไลบรารี parser JSON เองจะดีกว่า 90% แรกอาจเขียนได้ใน 45 นาที แต่ 10% ที่เหลือต้องใช้แรงงานคนเป็นหมื่นและเวลาหลายพันชั่วโมง
    • มีแหล่งข้อมูลชื่อดังชื่อ Parsing JSON is a Minefield (2016) https://seriot.ch/projects/parsing_json.html
    • ดูเหมือนจะหา design ที่เป็นกลางได้ยากเท่าไลบรารีนี้ มันแค่ไล่คืนค่าคีย์และ item ใน array ทีละตัว แล้วใช้ string-slice แยกประเภทก่อนส่งกลับ
    • โปรเจ็กต์นี้ชูจุดเด่นเรื่อง zero-allocation และเก็บ state ให้น้อยที่สุด แต่สำหรับ type ที่ใช้งานบ่อยที่สุดอย่าง strings ก็ยังต้องมี allocation อยู่ดี มันเป็นทางเลือกที่ต่างจากปัญหาของฉันมาก
    • หวังว่า S-Expressions (sexp) จะยังคงมีคนรักต่อไป
  • น่าสนใจดี แต่ฉันสงสัยว่าไลบรารีนี้ผ่าน conformance test ไปได้แค่ไหน https://github.com/nst/JSONTestSuite
    • ในแง่ validation ดูเหมือนว่าไลบรารีนี้จะไม่เข้มงวดนัก เช่น มันยอมรับทั้ง ] และ } สำหรับปิด object หรือ array และยังนับ \v เป็น whitespace ด้วย เลยผ่อนปรนกว่ามาตรฐาน เหมาะจะมองว่าเป็น “ตัวดึงข้อมูลสำหรับ JSON ที่ถูกต้อง” มากกว่า แต่การต้องเขียน parser สำหรับ string หรือ number เองก็อาจน่ารำคาญ โดยเฉพาะเมื่อจำเป็นต้องตกลงกับฝั่งผู้ผลิตข้อมูลเรื่อง subset ของไวยากรณ์ JSON
    • การสร้าง conformance test จาก implementation จริง ๆ น่าจะมีประโยชน์มาก นั่นคือทำ test-set ที่ตรงกับ subset/superset ของพฤติกรรม parser ตัวใดตัวหนึ่งแบบเป๊ะ ๆ เพื่อหลีกเลี่ยงความเสี่ยงจากการที่ parser สองตัวตีความ JSON เดียวกันต่างกันแล้วก่อให้เกิดช่องโหว่ เช่น การ bypass การยืนยันตัวตน
    • ถามจริง ๆ เลยนะ มันรองรับ nested object ไหม
  • มันดูเหมือน parser ครึ่ง ๆ กลาง ๆ นิดหน่อย เพราะไม่ได้แปลงตัวเลขเป็น float หรือ int ดังนั้นคงต้องเขียนส่วนนั้นเพิ่มเอง แต่ก็น่าประทับใจพอที่ฉันยินดีจะใช้ แค่อยากชี้ประเด็นนี้ไว้
  • ฉันสงสัยว่า C99 ระบุไหมว่า struct นี้ถูก initialize เป็น 0 โดยปริยาย หรือแค่ลืม = { 0 } ไป จากโค้ดที่เกี่ยวข้อง ดูเหมือนว่า r->depth อาจยังไม่ถูก initialize และอาจบังเอิญเท่ากับ depth ในลูป sj__discard_until ได้
  • ฉันสงสัยว่าจุดประสงค์ของไลบรารีแบบนี้คืออะไร ในเมื่อดูเหมือนจะมีไลบรารี JSON ดี ๆ อยู่แล้วมากมาย หรือมันเป็นเครื่องมือเพื่อการเรียนรู้?
    • มันรวมเข้ากับโค้ดเบสเดิมได้ง่าย ขนาดเล็ก ไม่ใช้ heap allocation และไม่พึ่ง stdlib ด้วยซ้ำ (include แค่ stdbool.h และ stddef.h เพื่อประกาศ type) ไม่มีลูกเล่น template แบบ C++ และ API ก็ตรงไปตรงมา ไลบรารี C ที่ตรงเงื่อนไขพวกนี้ครบทุกข้อมีน้อยมาก และใน C++ ยิ่งหายากกว่า
    • การ parse โดยไม่มี overhead และไม่มี allocation นี่แหละที่น่าสนใจ มีประโยชน์ถ้าต้องการดึงเฉพาะคุณสมบัติบางตัวจาก json dump ขนาดใหญ่ เช่น Wikidata dumps
    • ใช้กับ embedded CPU ได้ทันที ทุกวันนี้ดูเหมือนแม้แต่พวก vape ก็อาจรัน API server ได้แล้ว
    • เพราะโค้ดเล็ก เลยตรวจสอบได้ง่ายแม้ในโปรเจ็กต์ที่เข้มงวดเรื่องความปลอดภัย และเรื่อง license compliance ก็ง่ายมากด้วย (ไม่ต้องมีข้อความแจ้งเตือน)
    • มันน่าจะเป็นโค้ดเบสที่ดีสำหรับมือใหม่ไว้ศึกษา หรือสำหรับคนที่อยาก parse อะไรง่าย ๆ ถ้าต้องการ code footprint เล็กในโปรเจ็กต์งานอดิเรกขนาดเล็กที่ทรัพยากรประมวลผลจำกัด แต่ถ้าเป็นกรณีนั้นฉันก็คงเลือกฟอร์แมตไฟล์อย่าง TOML มากกว่าอยู่ดี
  • เจ๋งดี! ครั้งหน้าถ้าต้องใช้ parser JSON ใน C ฉันคงลองดู ฉันใช้ cJSON(https://github.com/DaveGamble/cJSON) มาหลายปีแล้วแบบไม่มีปัญหา ก่อนหน้านั้นเคยใช้ wjelement(https://github.com/netmail-open/wjelement) แต่สุดท้ายก็เปลี่ยนเพราะมีปัญหาบางอย่าง (จำเหตุผลเจาะจงไม่ได้แล้วเพราะนานมาก)
  • จะเรียกสิ่งนี้ว่า parser ก็ดูฝืน ๆ ไปหน่อย ปกติดูใกล้เคียง lexer มากกว่า