JSON Jamboree: ศึกชิงชัย Deserialization แบบคงที่กับแบบไดนามิกสำหรับ Apex Callout
บทนำ:
สวัสดีครับ! ผม Steve Simpson, Salesforce Certified Technical Architect และผู้สอน ในบล็อกนี้ เราจะสำรวจเทคนิคต่างๆ ในการแยกวิเคราะห์ JSON เมื่อทำ Apex callout ใน Salesforce เราจะพูดถึงการแยกวิเคราะห์ JSON แบบคงที่ แบบไดนามิก และแนวทางผสมที่ให้ทั้งความยืดหยุ่นและความง่าย
ส่วนที่ 1: การแยกวิเคราะห์ JSON แบบคงที่ด้วย Deserialization บรรทัดเดียว
เมื่อจัดการกับโครงสร้าง JSON ที่ตรงไปตรงมาและไม่เปลี่ยนบ่อย คุณสร้างคลาส Apex ที่ตรงกับโครงสร้าง JSON พอดีได้ วิธีนี้ให้คุณใช้โค้ดบรรทัดเดียวเพื่อ deserialize สตริง JSON เป็นอาร์เรย์ วิธีนี้ทรงพลังและมีประสิทธิภาพสำหรับโครงสร้าง JSON ที่คงที่

public class OS_FlightStatusModel{ public String icao24 {get;set;} public Integer firstSeen {get;set;} public String estDepartureAirport {get;set;} public Integer lastSeen {get;set;} public String estArrivalAirport {get;set;} public String callsign {get;set;} public Integer estDepartureAirportHorizDistance {get;set;} public Integer estDepartureAirportVertDistance {get;set;} public Integer estArrivalAirportHorizDistance {get;set;} public Integer estArrivalAirportVertDistance {get;set;} public Integer departureAirportCandidatesCount {get;set;} public Integer arrivalAirportCandidatesCount {get;set;} //---Use the JSON Deserialize public static List<OS_FlightStatusModel> parse(String json) { return (List<OS_FlightStatusModel>) System.JSON.deserialize(json, List<OS_FlightStatusModel>.class); }}ด้วยคลาสข้างต้น คุณแยกวิเคราะห์ JSON ได้ง่ายๆ ด้วยโค้ดบรรทัดเดียว:
//---Get the Flight Request private static List<OS_FlightStatusModel> getFlightRequest(String requestURL) {//---Return structureList<OS_FlightStatusModel> retList = new List<OS_FlightStatusModel>();//---Make the requestHttpResponse resp = getRequest( requestURL);//---Check for SuccessInteger respStatusCode = resp.getStatusCode();if (respStatusCode == 200) {//---Read the PayloadString payload = resp.getBody();debug('Payload: ' + payload);//---Parse the payloadretList = OS_FlightStatusModel.parse(payload);if (retList != null) {System.debug('Found ' + retList.size() + ' records');for( OS_FlightStatusModel mRow : retList) {debug('Row: ' + mRow); } } }else {//---Handle an errordebug('Response Status Code: ' + respStatusCode); }return retList; }ส่วนที่ 2: การแยกวิเคราะห์ JSON แบบไดนามิก
สำหรับโครงสร้าง JSON ที่ซับซ้อนขึ้น เช่น อาร์เรย์หรือออบเจกต์ซ้อน คุณอาจต้องใช้การแยกวิเคราะห์ JSON แบบไดนามิก วิธีนี้ต้องใช้โค้ดหลายบรรทัดขึ้นและการไล่ไปตาม token ของ JSON อย่างระมัดระวัง แม้อาจดูซับซ้อนกว่า การแยกวิเคราะห์ JSON แบบไดนามิกก็จำเป็นเมื่อจัดการกับโครงสร้าง JSON ขั้นสูง
ตัวอย่าง:
/***************************************************************************************** @Name OS_StateModelParser* @Author Steve Simpson - steve@stevetecharc.com* @Date 2023** @Description Parser for reading State Models from JSON***************************************************************************************** Version Developer Date Description*----------------------------------------------------------------------------------------* 1.0 Steve Simpson 2023 Initial Creation****************************************************************************************/public with sharing class OS_StateModelParser extends OS_BaseParser{private OS_StateResponseModel responseModel {get; set;}//--Parse the payload into the OS_StateResponseModelpublic OS_StateResponseModel parse(String json) {//---Setup the return valueresponseModel = new OS_StateResponseModel();responseModel.States = new List<OS_StateModel>();try {parseBody(json); }catch (Exception ex) {debug( 'Exception: ' + ex.getMessage() + ' ' + ex.getStackTraceString());debugJSON(json); }return responseModel; }//---Parse the body of the JSONprivate void parseBody(String json) {//---Set the default Page Sizeif (PageSize == 0) PageSize = DEFAULT_PAGE_SIZE;Boolean hasTokenTime = false;Boolean hasTokenStates = false;JSONParser parser = System.JSON.createParser(json);//---Read Token by Tokenwhile (parser.nextToken() != System.JSONToken.END_OBJECT) {//---Read the current Token, check its field nameif (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {String text = parser.getText();debug( 'Json Token: ' + text);//---If the next token has a value, then write it, this handles null valuesif (parser.nextToken() != System.JSONToken.VALUE_NULL) {//---Read the timeif (text == 'time') {responseModel.ResponseTime = parser.getIntegerValue();hasTokenTime = true; }//---Read the statesif (text == 'states') {responseModel.States = parseStateList(parser);hasTokenStates = true; } } }//---If have both tags, then break out of loopif (hasTokenStates && hasTokenTime) break; } }//---Parse the Statesprivate List<OS_StateModel> parseStateList(JSONParser parser) {CurrentRecord = 0;//---Build the return listList<OS_StateModel> retList = new List<OS_StateModel>();//---Go to the next token if neededif (parser.getCurrentToken() == null) parser.nextToken();//---Iterate over the itemswhile (parser.nextToken() != System.JSONToken.END_ARRAY) {CurrentRecord++; //---Track the current recordString logLine = 'Rec: ' + CurrentRecord + ': ';if (CurrentRecord > getMaxRecord()) {//---If past the max record, then break out of loopdebug(logLine + 'End of page');break; }else if (CurrentRecord >= getFirstRecordToProcess()) {//---Build a new State RowOS_StateModel sModel = new OS_StateModel();parseStateItem(parser, sModel);retList.add(sModel);debug(logLine + 'State: ' + sModel); }else {//---Pass in null model to trigger token advancement without reading valuesparseStateItem(parser, null);debug(logLine + 'Skipped'); } }return retList; }//---Parse the Statesprivate void parseStateItem(JSONParser parser, OS_StateModel sModel) {//---Go to the next token if neededif (parser.getCurrentToken() == null) parser.nextToken();Integer position = 0;while (parser.nextToken() != System.JSONToken.END_ARRAY) {if (sModel != null && parser.getCurrentToken() != System.JSONToken.VALUE_NULL) {if (position == 0) sModel.icao24 = parser.getText();if (position == 1) sModel.callsign = parser.getText();if (position == 2) sModel.origin_country = parser.getText();if (position == 3) sModel.time_position = parser.getIntegerValue();if (position == 4) sModel.last_contact = parser.getIntegerValue();if (position == 5) sModel.longitude = parser.getDoubleValue();if (position == 6) sModel.latitude = parser.getDoubleValue();if (position == 7) sModel.baro_altitude = parser.getDoubleValue();if (position == 8) sModel.on_ground = parser.getBooleanValue();if (position == 9) sModel.velocity = parser.getDoubleValue();if (position == 10) sModel.true_track = parser.getDoubleValue();if (position == 11) sModel.vertical_rate = parser.getDoubleValue();if (position == 12) {//---Sensors, sub Array, should be null }if (position == 13) sModel.geo_altitude = parser.getDoubleValue();if (position == 14) sModel.squawk = parser.getText();if (position == 15) sModel.spi = parser.getBooleanValue();if (position == 16) sModel.position_source = parser.getIntegerValue();if (position == 17) sModel.category = parser.getIntegerValue(); }position++;//System.debug('Position ' + position); } }}ส่วนที่ 3: การจับคู่แบบไดนามิกด้วย Custom Metadata
ถ้าคุณต้องการความยืดหยุ่นมากขึ้นอีก ให้พิจารณาการจับคู่แบบไดนามิกด้วย custom metadata วิธีนี้ให้คุณจับคู่ฟิลด์ JSON กับฟิลด์ SObject แบบไดนามิกโดยไม่ต้องเปลี่ยนโค้ด ด้วยการจับคู่แบบไดนามิก คุณเพิ่มหรือลบฟิลด์ได้ทันที ให้ความยืดหยุ่นสูงในสภาพแวดล้อม production
ตัวอย่าง:
สร้าง custom metadata ที่จับคู่ฟิลด์ JSON กับชื่อ API ของ SObject ตำแหน่ง และประเภทข้อมูล
โหลด custom metadata แล้วใช้มันกำหนดค่าให้ฟิลด์ SObject แบบไดนามิก
/***************************************************************************************** @Name OS_StateSObjectModelParser* @Author Steve Simpson - steve@stevetecharc.com* @Date 2023** @Description Parser for reading State Models from JSON***************************************************************************************** Version Developer Date Description*----------------------------------------------------------------------------------------* 1.0 Steve Simpson 2023 Initial Creation****************************************************************************************/public with sharing class OS_StateSObjectModelParser extends OS_BaseParser{private OS_StateSObjectResponseModel responseModel {get; set;}private List<OS_StateMap__mdt> mapList {get;set;}//--Parse the payload into the OS_StateResponseModelpublic OS_StateSObjectResponseModel parse(String json) {mapList = OS_StateMap__mdt.getAll().values();//---Setup the return valueresponseModel = new OS_StateSObjectResponseModel();responseModel.States = new List<OS_State__c>();try {parseBody(json); }catch (Exception ex) {debug( 'Exception: ' + ex.getMessage() + ' ' + ex.getStackTraceString());debugJSON(json); }return responseModel; }//---Parse the body of the JSONprivate void parseBody(String json) {//---Set the default Page Sizeif (PageSize == 0) PageSize = DEFAULT_PAGE_SIZE;Boolean hasTokenTime = false;Boolean hasTokenStates = false;JSONParser parser = System.JSON.createParser(json);//---Read Token by Tokenwhile (parser.nextToken() != System.JSONToken.END_OBJECT) {//---Read the current Token, check its field nameif (parser.getCurrentToken() == System.JSONToken.FIELD_NAME) {String text = parser.getText();debug( 'Json Token: ' + text);//---If the next token has a value, then write it, this handles null valuesif (parser.nextToken() != System.JSONToken.VALUE_NULL) {//---Read the timeif (text == 'time') {responseModel.ResponseTime = parser.getIntegerValue();hasTokenTime = true; }//---Read the statesif (text == 'states') {responseModel.States = parseStateList(parser);hasTokenStates = true; } } }//---If have both tags, then break out of loopif (hasTokenStates && hasTokenTime) break; } }//---Parse the Statesprivate List<OS_State__c> parseStateList(JSONParser parser) {CurrentRecord = 0;//---Build the return listList<OS_State__c> retList = new List<OS_State__c>();//---Go to the next token if neededif (parser.getCurrentToken() == null) parser.nextToken();//---Iterate over the itemswhile (parser.nextToken() != System.JSONToken.END_ARRAY) {CurrentRecord++; //---Track the current recordString logLine = 'Rec: ' + CurrentRecord + ': ';if (CurrentRecord > getMaxRecord()) {//---If past the max record, then break out of loopdebug(logLine + 'End of page');break; }else if (CurrentRecord >= getFirstRecordToProcess()) {//---Build a new State RowOS_State__c osState = new OS_State__c();parseStateItem(parser, osState);retList.add(osState);debug(logLine + 'State: ' + osState); }else {//---Pass in null model to trigger token advancement without reading valuesparseStateItem(parser, null);debug(logLine + 'Skipped'); } }return retList; }//---Parse the Statesprivate void parseStateItem(JSONParser parser, OS_State__c osState) {//---Go to the next token if neededif (parser.getCurrentToken() == null) parser.nextToken();Integer position = 0;while (parser.nextToken() != System.JSONToken.END_ARRAY) {if (osState != null && parser.getCurrentToken() != System.JSONToken.VALUE_NULL) {OS_StateMap__mdt curMap = null;for(OS_StateMap__mdt mapRow : mapList) {if (mapRow.Position__c == position) {curMap = mapRow;break; } }if (curMap != null) {String apiName = curMap.API_Name__c;if (curMap.Data_Type__c == 'Text') {osState.put(apiName, parser.getText()); }if (curMap.Data_Type__c == 'Integer') {osState.put(apiName, parser.getIntegerValue()); }if (curMap.Data_Type__c == 'Double') {osState.put(apiName, parser.getDoubleValue()); }if (curMap.Data_Type__c == 'Boolean') {osState.put(apiName, parser.getBooleanValue()); } } }position++;//System.debug('Position ' + position); } }}บทสรุป:
ใน Apex callout ของ Salesforce คุณเลือกเทคนิคการแยกวิเคราะห์ JSON ได้หลายแบบตามความต้องการ สำหรับ JSON ที่ตรงไปตรงมาและคงที่ ใช้การแยกวิเคราะห์แบบคงที่ด้วย deserialization บรรทัดเดียว สำหรับโครงสร้างที่ซับซ้อนขึ้น พิจารณาการแยกวิเคราะห์ JSON แบบไดนามิก และเพื่อความยืดหยุ่นสูงสุด ใช้การจับคู่แบบไดนามิกด้วย custom metadata แต่ละวิธีมีกรณีการใช้งานของตัวเอง จึงเลือกแบบที่เหมาะกับความต้องการของโปรเจกต์คุณที่สุด
ขอให้สนุกกับการเขียนโค้ด!
ติดตามต่อ
สำหรับมุมมองและเคล็ดลับเพิ่มเติม ติดตามได้ที่ www.SteveTechArc.com และช่อง YouTube @SteveTechArc (opens in a new tab) กดติดตามและเพิ่มความเข้าใจเรื่อง Salesforce และวิธีผสานรวมมันกับระบบอื่น
ช่วยเปลี่ยนโลกด้วยการแบ่งปันความรู้ด้านการผสานรวมกับเพื่อนสถาปนิกและผู้ที่อยู่บนเส้นทางสู่การเป็นสถาปนิก!
บทถอดความได้รับความช่วยเหลือจาก AI
STA 1.6