ในกระบวนการพัฒนาเว็บการโต้ตอบข้อมูลเป็นสิ่งที่ขาดไม่ได้ซึ่งต้องการรูปแบบที่เกี่ยวข้องของข้อมูลแบบโต้ตอบที่จะระบุเพื่อให้ข้อมูลสามารถส่งผ่านระหว่างไคลเอนต์และเซิร์ฟเวอร์ โดยปกติจะมีรูปแบบข้อมูลสองประเภท: 1. XML; 2. Json. โดยทั่วไปแล้ว JSON ใช้เพื่อส่งผ่านข้อมูล บทความนี้แนะนำปัญหาหลายประการที่พบเมื่อแปลง JSON และวัตถุใน Java และคำแนะนำที่เกี่ยวข้อง
ก่อนอื่นเรามีสองแนวคิดสำหรับ JSON:
วัตถุ JSON (สัญกรณ์วัตถุ JavaScript, สัญลักษณ์วัตถุ JavaScript) นี่ดูเหมือนจะเป็นบิต JavaScript ที่กำหนดเอง แต่เป็นภาษาและแพลตฟอร์มเฉพาะเป็นไวยากรณ์ มันหมายความว่าโดยปกติเมื่อเราส่งข้อมูลไปยังฝั่งเซิร์ฟเวอร์ (เบราว์เซอร์) เราใช้รูปแบบ JSON และรูปแบบนี้ใช้เพื่อแสดงวัตถุ JavaScript ประกอบด้วยชุดของ "ค่าคีย์" เช่น {"id": 1, "ชื่อ": "Kevin"} ซึ่งค่อนข้างคล้ายกับวิธีการจัดเก็บคู่คีย์-ค่าของแผนที่ วัตถุ JSON ที่อธิบายไว้ใน Java จริง ๆ แล้วหมายถึงคลาส JSONObject ซึ่งมักจะตั้งชื่อตามชื่อนี้ในแพ็คเกจ JSONJAR ของบุคคลที่สาม แพ็คเกจ JAR ที่แตกต่างกันมีการใช้งานภายในที่แตกต่างกันเล็กน้อย
สตริง JSON การแปลงระหว่างวัตถุ JSON และสตริง JSON เป็นกระบวนการของการทำให้เป็นอนุกรมและ deserialization ซึ่งเป็นเหมือนการทำให้เป็นอนุกรมและ deserialization ของวัตถุ Java การส่งข้อมูลในเครือข่ายดำเนินการผ่านสตริงหรือสตรีมไบนารี ฯลฯ นั่นคือเมื่อไคลเอนต์ (เบราว์เซอร์) ต้องส่งข้อมูลในรูปแบบ JSON สตริงจะถูกส่งผ่านไปยังเครือข่ายในเวลานี้ บางครั้งจำเป็นต้องแปลงสตริง JSON เป็นวัตถุ JSON จากนั้นดำเนินการต่อไป (ประเภทสตริงจะถูกแปลงเป็นประเภท JSONObject)
แนวคิดสองแนวคิดข้างต้นชี้แจงรูปแบบข้อมูลของ JSON หรือเรียกว่าไวยากรณ์ JSON มีแพ็คเกจ JAR มากมายสำหรับ JSON ใน Java "ที่ใช้กันทั่วไปมากที่สุด" คือแพ็คเกจ JAR ที่จัดทำโดย "net.sf.json" บทความนี้จะมุ่งเน้นไปที่แพ็คเกจหลุมนี้ แม้ว่ามันจะเป็นหลุม แต่ก็มีแอพพลิเคชั่นที่หลากหลาย ในความเป็นจริงมีแพ็คเกจ JSON ที่ยอดเยี่ยมอื่น ๆ ให้เราใช้เช่น Fastjson ซึ่งอาลีบาบาอ้างว่าเป็นแพ็คเกจ JSON ที่เร็วที่สุด, GSON ของ Google และ Jackson ลองใช้แพ็คเกจ "net.sf.json" ไม่เพียง แต่จะมีข้อผิดพลาดใด ๆ แต่ยังเก่ามากเช่นกันจนไม่สามารถดาวน์โหลดซอร์สโค้ดในแนวคิดได้ ที่เก็บ Maven แสดงให้เห็นว่ามันหยุดในเวอร์ชัน 2.4 ในปี 2010 มาพูดคุยเกี่ยวกับข้อบกพร่องสองข้อที่ฉันรู้แล้วเกี่ยวกับ“ net.sf.json” และวิธีการสร้างข้อบกพร่องทั้งสองนี้
แพ็คเกจ JSON PIT ใน java - net.sf.json
1. เมื่อวัตถุ Java แปลงวัตถุ JSON วิธีการทั้งหมดเริ่มต้นด้วย GET จะถูกแปลง
ตัวอย่างเช่นสิ่งนี้หมายความว่ามีวัตถุ Java ต่อไปนี้
แพ็คเกจ sfjson; นำเข้า java.util.list;/*** สร้างโดย Kevin เมื่อวันที่ 2017/12/1 */นักเรียนชั้นเรียนสาธารณะ {ID INT ส่วนตัว; รายการส่วนตัว <Long> แน่นอน สาธารณะ int getId () {return id; } โมฆะสาธารณะ setId (int id) {this.id = id; } รายการสาธารณะ <long> getCourseIds () {return courseids; } โมฆะสาธารณะ setCourseIds (รายการ <lont> courseids) {this.courseIds = courseids; } สตริงสาธารณะ getSql () {// วิธีการรับคำสั่ง SQL ในคลาสนี้ไม่มีฟิลด์แอตทริบิวต์ที่สอดคล้องกันคืน "นี่คือ SQL"; -เมื่อเราแปลงวัตถุนักเรียนเป็นวัตถุ JSON เราหวังว่ารูปแบบ JSON ที่แปลงแล้วควรเป็น:
{"id": 1, "courseids": [1, 2, 3]}อย่างไรก็ตามผลลัพธ์หลังจากการแปลง JsonObject json = jsonObject.fromobject (นักเรียน); API คือ:
กล่าวอีกนัยหนึ่งก็สามารถเดาได้ว่า "net.sf.json" ได้รับวิธีการที่เริ่มต้นด้วยตัวดัดแปลงสาธารณะได้รับในวัตถุ Java และกำหนดคำต่อท้ายเป็น "กุญแจ" ของวัตถุ JSON และกำหนดค่าการส่งคืนของวิธีการที่เริ่มต้นด้วยการรับเป็น "มูลค่า" ของคีย์ที่สอดคล้องกัน โปรดทราบว่ามันเป็นวิธีที่เริ่มต้นด้วยตัวดัดแปลงสาธารณะและมีค่าคืน
ฉันคิดว่านี่เป็นกฎการแปลงที่ไม่มีเหตุผล ถ้าฉันกำหนดวิธีการในวัตถุ Java และเพียงเพราะวิธีนี้เริ่มต้นด้วย "รับ" และมีค่าคืนมันจะถูกเปิดเผย? หรือมันสัมผัสกับคอนโซลคอนโซลส่วนหน้าเมื่อมันถูกส่งกลับไปยังไคลเอนต์ (เบราว์เซอร์)? ผู้เขียนกำหนดกฎการแปลงนี้ เหตุผลคร่าวๆที่ฉันคิดว่า: เนื่องจากคุณนิยามว่าเป็นวิธีการสาธารณะและตั้งชื่อมันได้รับมันเป็นการเปิดเผยวิธีนี้โดยเจตนาเพื่อให้ลูกค้าเรียกมันว่ามีสิทธิ์ที่จะได้รับ แต่ฉันก็ยังคิดว่าสิ่งนี้ไม่มีเหตุผลและแม้ฉันจะนิยามว่ามันเป็นข้อผิดพลาด มันอาจจะไม่สมเหตุสมผลสำหรับฉันที่จะกำหนดวิธีนี้เพราะตามการทดสอบจริงของฉันไม่เพียง แต่แพ็คเกจ "net.sf.json" จะถูกแปลงตามกฎนี้ แต่ Fastjson และ Jackson ยังทำตามกฎนี้ แต่ GSN ของ Google ไม่ได้แปลงวัตถุเป็น JSON ตามกฎนี้
แปลงวัตถุนักเรียนที่สร้างขึ้นเป็นวัตถุ JSON ผ่าน jsonObject json = jsonObject.FromObject (นักเรียน); และนักเรียนตามที่อธิบายไว้ข้างต้น หลังจากป้อนวิธีนี้วิธีการโอเวอร์โหลดจากวัตถุ (วัตถุ, jsonConfig) จะยังคงถูกเรียกต่อไป ในวิธีการโอเวอร์โหลดนี้อินสแตนซ์จะถูกใช้เพื่อตรวจสอบว่าวัตถุวัตถุที่ถูกแปลงคือการแจงนับคำอธิบายประกอบและประเภทอื่น ๆ ประเภทพิเศษเหล่านี้จะมีวิธีการตัดสินพิเศษ นี่คือวัตถุ Java Pojo ธรรมดาดังนั้นมันจะเข้าสู่ _FromObject (Object, JSONCONFIG) และจะมีการตัดสินบางอย่างในวิธีนี้และในที่สุดวัตถุ JSON ถูกสร้างขึ้นโดยการเรียกค่าเริ่มต้นการประมวลผล วิธีนี้เป็นกุญแจสำคัญและจะยังคงได้รับ "คุณสมบัติตัวบ่งชี้" ผ่านวิธีการ PropertyUtils.getPropertyDescriptors (Bean) ในความเป็นจริงมันคือการได้รับวิธีการด้วย GET ซึ่งถูกห่อหุ้มเป็น propertyDescriptor ที่นี่ ชั้นเรียนนักเรียนนี้จะได้รับ 4 คือ: getclass, getid, getCourseids, getSql
ในความเป็นจริง PropertyDescriptor ถูกห่อหุ้มอย่างละเอียดและมีการกำหนดวิธีการอ่านและเขียนทั้งหมดแล้ว
ตัวอย่างเช่นวิธี GetSQL นี้ได้รับการแยกวิเคราะห์ลงใน PropertyDescriptor ในรูปด้านบน ต่อไปนี้จะถูกกรองวิธีบางอย่างผ่านคลาสนี้ ตัวอย่างเช่นวิธี getClass ไม่ใช่วิธีการใน POJO ดังนั้นจึงไม่จำเป็นต้องแปลงเป็นวัตถุ JSON PropertyDescriptor ได้รับผ่าน beaninfo#getPropertyDescriptors และ beaninfo ได้รับผ่านผู้เข้าร่วมใหม่ (beanclass, null, use_all_beaninfo) .getBeanInfo (); จากนั้นคุณจะไปถึงวิธีการต่อไปนี้ในที่สุด
private beaninfo getBeanInfo () พ่น IntrospectionException {… methodDescriptor mds [] = getTargetMethodinfo (); // วิธีนี้จะเรียก getPublicDeclaredMethods คุณจะเห็นว่ามันกำลังมองหาวิธีการสาธารณะและวิธีการสาธารณะทั้งหมดรวมถึงการรอและทรัพย์สินอื่น ๆ PDS [] = getTargetPropertyInfo (); // ตัวกรองตามกฎบางอย่าง กฎการกรองทั้งหมดอยู่ในวิธีนี้ซึ่งคือการเลือกวิธีการที่ตัวดัดแปลงสาธารณะมีคำนำหน้ารับและค่าส่งคืน ...ฉันวิเคราะห์ซอร์สโค้ดของ net.sf.json สั้น ๆ และพบว่าในขณะที่ฉันเดาว่าซอร์สโค้ดเฉพาะนั้นค่อนข้างใหญ่และ จำกัด ในอวกาศและจำเป็นต้องดูและติดตามด้วยตัวเอง
2. เมื่อแปลงวัตถุ Java เป็นวัตถุ JSON ข้อผิดพลาดการแปลงจะเกิดขึ้นในรายการ <long>
ชื่อไม่สามารถอธิบายได้อย่างชัดเจนในประโยคเดียวและฉันมั่นใจว่าปัญหานี้เป็นข้อผิดพลาด
ตอนนี้มีสตริง JSON ของ {"ID": 1, "CourseIds": [1,2,3]} และจำเป็นต้องถูกแปลงเป็นวัตถุนักเรียนที่กล่าวถึงข้างต้น มีสองฟิลด์แอตทริบิวต์ของประเภท int และรายการ <long> ในวัตถุนักเรียนซึ่งหมายความว่าสตริง JSON นี้ควรถูกแปลงเป็นประเภทข้อมูลที่เกี่ยวข้อง
String json = "{/" id/": 1,/" courseids/": [1,2,3]}" นักเรียนนักเรียน = (นักเรียน) jsonobject.tobean (jsonobject.fromobject (json), นักเรียน); system.out.println (นักเรียนเอาต์พุตข้างต้นควรเป็นจริง แต่น่าเสียดายที่มันเป็นเท็จ เพื่อความแม่นยำมันเป็นเวลาที่รวบรวมได้นาน แต่จำนวนเต็มในเวลาทำงาน สิ่งนี้จะต้องกล่าวกันว่าเป็นข้อผิดพลาดและไม่มีแพ็คเกจ JSON อีกสามชุดที่มีข้อผิดพลาดดังกล่าว ดังนั้นฉันแน่ใจว่ามันเป็นข้อผิดพลาด มาดูกันว่าข้อผิดพลาดนี้เกิดขึ้นได้อย่างไรใน net.sf.json คุณต้องเปรียบเทียบซอร์สโค้ดด้วยตัวเองเพื่อดู ในขณะที่ฉันได้ทำการดีบักเบรกพอยต์ให้ลึกลงไปเรื่อย ๆ ฉันค้นพบว่าเมื่อ net.sf.json กำลังประมวลผลข้อมูลจำนวนเต็มฉันค้นพบวิธีนี้ numberutils#createnumber คลาสนี้ตัดสินชนิดข้อมูลเมื่อดึงข้อมูลจากสตริง ความตั้งใจดั้งเดิมคือการประมวลผลจำนวนนานหากมี "L" หรือ "L" หลังจากนั้น จากมุมมองนี้ผลลัพธ์สุดท้ายควรถูกต้อง
กรณี 'l': กรณี 'l': ถ้า (dec == null && exp == null && (numeric.charat (0) == '-' && isdigits (numeric.substring (1)) || isdigits (ตัวเลข))) {ลอง {return createLong (ตัวเลข); } catch (numberFormatexception var11) {return createBiginteger (ตัวเลข); }} else {โยน numberFormatexception ใหม่ (str + "ไม่ใช่หมายเลขที่ถูกต้อง"); -แท้จริงแล้ว Net.sf.json ได้ตัดสินชนิดข้อมูลอย่างแม่นยำผ่านตัวระบุหลังจากจำนวน ปัญหาอยู่ที่ความจริงที่ว่าหลังจากได้รับค่านี้และชนิดข้อมูลของมันจะต้องเก็บไว้ใน JsonObject วิธีการ jsonutils#transformnumber มีอยู่ระหว่างกระบวนการจัดเก็บ การมีอยู่ของวิธีนี้อย่างน้อยก็ฟุ่มเฟือยอย่างหมดจดในมุมมองปัจจุบัน
หมายเลขคงที่สาธารณะ transformNumber (อินพุตหมายเลข) {if (อินสแตนซ์อินพุตของ Float) {ส่งคืนใหม่ double (input.toString ()); } อื่นถ้า (อินสแตนซ์อินพุทสั้น) {ส่งคืนจำนวนเต็มใหม่ (input.intValue ()); } อื่นถ้า (อินสแตนซ์อินพุทไบต์) {ส่งคืนจำนวนเต็มใหม่ (input.intValue ()); } else {if (อินสแตนซ์อินพุทยาว) {long max = new long (2147483647l); if (input.longValue () <= max.longValue () && input.longValue ()> = -2147483648l) {// แม้ว่าประเภทดั้งเดิมจะยาวตราบใดที่มันอยู่ในช่วงจำนวนเต็ม ส่งคืนจำนวนเต็มใหม่ (input.intvalue ()); }} ส่งคืนอินพุต; -รหัสด้านบนแสดงให้เห็นอย่างชัดเจนว่าผู้กระทำผิดไม่ว่าจะเป็นประเภทยาว (ประเภทยาวภายในช่วงจำนวนเต็ม) รวมถึงไบต์และสั้นมันจะถูกแปลงเป็นจำนวนเต็ม ฉันไม่เข้าใจว่าความหมายของรหัสนี้คืออะไร ต้องกำหนดชนิดข้อมูลที่ถูกต้องตามตัวอักษรหลังจากหมายเลขและชนิดข้อมูลที่ถูกต้องจะต้องถูกแปลงอีกครั้งในภายหลังซึ่งนำไปสู่ข้อผิดพลาดที่กล่าวถึงในตอนต้น ปัญหานี้แทบจะหลีกเลี่ยงไม่ได้ดังนั้นวิธีที่ดีที่สุดคือไม่ใช้มัน
ข้อผิดพลาดทั้งสองนี้ถูกค้นพบโดยบังเอิญ ขอแนะนำไม่ให้ใช้แพ็คเกจ JSON ของ net.sf.json ที่ยังไม่ได้รับการดูแลรักษา นอกจากนี้แพ็คเกจ net.sf.json ยังไม่เข้มงวดในการตรวจสอบรูปแบบ JSON หากรูปแบบดังกล่าวคือ "{" id ": 1," courseids ":" [1,2,3] "}" ข้อยกเว้นจะถูกโยนลงในแพ็คเกจอีกสามแพ็คเกจ แต่ net.sf.json จะไม่
บทความข้างต้นกล่าวถึงข้อผิดพลาดของ JSON และการถ่ายโอนวัตถุใน Java โดยละเอียด นี่คือเนื้อหาทั้งหมดที่ฉันแบ่งปันกับคุณ ฉันหวังว่ามันจะให้ข้อมูลอ้างอิงและฉันหวังว่าคุณจะสนับสนุน wulin.com มากขึ้น