複雜JSON字串轉換為Java巢狀物件的實現
目錄
- 背景
- 方法
- 預備工作
- 構建物件模型
- 使用jackson 庫解析
- 使用GSON解析
- 不含列表的巢狀物件
背景
實際開發中,常常需要將比較複雜的 ON 字串轉換為對應的 物件。這裡記錄下解決方案。
如下所示,是入侵事件檢測得到的 JSON 串:
[{"rule_id":"反彈shell","format_output":"程序 pname 反向連線到 %dest_ip%:%dest_port%","info":{"process_events":{"pid":21,"pname":"nginx","cmdline":"curl www.cfda.com","ppid":7,"ppname":"bash"},"proc_trees":[{"pid":21,"ppname":"bash"}],"containers":{"container_id":"fef4636d8403871c2e56e06e51d609554564adbbf8284dd914a0f61130558bdf","container_name":"nginx","image_id":"4eb8f7c43909449dbad801c50d9dccc7dc86631e54f28b1a4b13575729065be8","status":"Running"},"sockets":{"src_ip":"127.0.0.1","src_port":"8080","type":"1","in_out":"0","dest_ip":"localhost","dest_port":"80"}}}]
方法
預備工作
把上述 json 串放在 src/test/resources 下,寫一個檔案讀寫程式來解析。 其實放在哪裡不重要,重要的是拿到這個 JSON 串便於後續解析。
public static String readFromSource(String filename) { try { InputStream is = RWTool.class.getResourceAsStream(filename); byte[] bytes = new byte[4096]; int num = 0; String json = ""; while((num=is.read(bytes))>0){ json=new String(bytes,num); } return json; } catch (Exception ex) { throw new RuntimeException(ex.getCause()); } }
構建物件模型
首先,要根據這個 JSON 字串解析出對應的資料模型 AgentDetectEventData。主要就是按照 JSON 串中的 key 的層次結構來建立。
@Getter @Setter public class AgentDetectEventData { @SerializedName("rule_id") @JsonProperty("rule_id") private String ruleId; @SerializedName("format_output") @JsonProperty("format_output") private String formatOutput; @SerializedName("info") @JsonProperty("info") private AgentDetectEventDetail info; } @Getter @Setter public class AgentDetectEventDetail { @SerializedName("process_events") @JsonProperty("process_events") private ProcessEvent processEvent; @SerializedName("proc_trees") @JsonProperty("proc_trees") private List<ProcessTree> procTree; @SerializedName("containers") @JsonProperty("containers") private Container container; @SerializedName("sockets") @JsonProperty("sockets") private Socket socket; } @Getter @Setter public class ProcessEvent { @SerializedName("pid") @JsonProperty("pid") private String pid; @SerializedName("pname") @JsonProperty("pname") private String pname; @SerializedName("cmdline") @JsonProperty("cmdline") private String cmdline; @SerializedName("ppid") @JsonProperty("ppid") private String ppid; @SerializedName("ppname") @JsonProperty("ppname") private String ppname; } @Getter @Setter public class ProcessTree { @SerializedName("pid") @JsonProperty("pid") private String pid; @SerializedName("pname") @JsonProperty("pname") private String pname; @SerializedName("cmdline") @JsonProperty("cmdline") private String cmdline; @SerializedName("ppid") @JsonProperty("ppid") private String ppid; @SerializedName("ppname") @JsonProperty("ppname") private String ppname; } @Getter @Setter public class Container { @SerializedName("container_id") @JsonProperty("container_id") private String containerId; @SerializedName("container_name") @JsonProperty("container_name") private String containerName; @SerializedName("image_id") @JsonProperty("image_id") private String imageId; @SerializedName("status") @JsonProperty("status") private String status; } @Getter @Setter public class Socket { @SerializedName("src_ip") @JsonProperty("src_ip") private String srcIp; @SerializedName("src_port") @JsonProperty("src_port") private String srcPort; @SerializedName("type") @JsonProperty("type") xmGelYQHLprivate String type; @SerializedName("in_out") @JsonProperty("in_out") private String inOut; @SerializedName("dest_ip") @JsonProperty("dest_ip") private String destIp; @SerializedName("dest_port") @JsonProperty("dest_port") private String destPort; }
這裡有兩個注意點:
- JSON 字串的欄位命名是下劃線形式,而 Java 物件的屬性命名是駝峰式的,這裡需要做一個欄位名對映轉換。 使用 Jackson 庫來轉換,是 @JsonProperty 註解; 使用 gson 庫來轉換,是 @SerializedName 註解。
- 需要加 getter / setter 方法。
物件模型建立後,就成功了一大半。接下來,就是使用 json 庫來解析了。
使用jackson 庫解析
public class JsonUtil {
private static Logger logger = LoggerFactory.getLogger(JsonUtil.class);
private static final ObjectMapper MAPPER = new ObjectMapper();
static {
// 為保持物件版本相容性,忽略未知的屬性
MAPPER.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES,false);
// 序列化的時候,跳過null值
MAPPER.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
// date型別轉化
SimpleDateFohttp://www.cppcns.comrmat fmt www.cppcns.com= new SimpleDatewww.cppcns.comFormat("yyyy-MM-dd HH:mm:ss");
MAPPER.setDateFormat(fmt);
}
/**
* 將一個json字串解碼為java物件
*
* 注意:如果傳入的字串為null,那麼返回的物件也為null
*
* @param json json字串
* @param cls 物件型別
* @return 解析後的java物件
* @throws RuntimeException 若解析json過程中發生了異常
*/
public static <T> T toObject(String json,Class<T> cls) {
if (json == null) {
return null;
}
try {
return MAPPER.readValue(json,cls);
} catch (Exception e) {
throw new RuntimeException(e.getCause());
}
}
public static <T> String objectToJson(T obj){
if(obj == null){
return null;
}
try {
return obj instanceof String ? (String) obj : MAPPER.writeValueAsString(obj);
} catch (Exception e) {
return null;
}
}
public static <T> T jsonToObject(String src,TypeReference<T> typeReference){
if(StringUtils.isEmpty(src) || typeReference == null){
return null;
}
try {
return (T)(typeReference.getType().equals(String.class) ? src : MAPPER.readValue(src,typeReference));
} catch (Exception e) {
logger.warn("Parse Json to Object error",e);
throw new RuntimeException(e.getCause());
}
}
public static <T> T jsonToObject(String src,Class<?> collectionClass,Class<?>... elementClasses){
JavaType javaType = MAPPER.getTypeFactory().constructParametricType(collectionClass,elementClasses);
try {
return MAPPER.readValue(src,javaType);
} catch (Exception e) {
logger.warn("Parse Json to Object error",e);
throw new RuntimeException(e.getCause());
}
}
}
單測:
public class JsonUtilTest { @Test public void testParseJson() { String json = RWTool.readFromSource("/json.txt"); List<AgentDetectEventData> ade = JsonUtil.jsonToObject(json,new TypeReference<List<AgentDetectEventData>>() {}); Assert.assertNotNull(ade); } @Test public void testParseJson2() { String json = RWTool.readFromSource("/json.txt"); List<AgentDetectEventData> ade = JsonUtil.jsonToObject(json,List.class,AgentDetectEventData.class); Assert.assertNotNull(ade); } }
引入POM依賴為:
<dependency> <groupId>org.codehaus.jackson</groupId> <artifactId>jackson-mapper-asl</artifactId> <version>1.9.4</version> </dependency>
使用GSON解析
public class GsonUtil { static GsonBuilder gsonBuilder = null; static { gsonBuilder = new GsonBuilder(); gsonBuilder.setDateFormat("yyyy-MM-dd HH:mm:ss"); } public static Gson getGson() { return gsonBuilder.create(); } public static <T> T fromJson(String json,Class<T> cls) { return getGson().fromJson(json,cls); } public static <T> T fromJson(String json,Type type) { return getGson().fromJson(json,type); } }
單測:
public class GsonUtilTest { @Test public void testParseJson() { String json = RWTool.readFromSource("/json.txt"); List<AgentDetectEventData> ade = GsonUtil.fromJson(json,new TypeToken<List<AgentDetectEventData>>(){}.getType()); Assert.assertNotNull(ade); } }
引入 POM 為:
<dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.3.1</version> </dependency>
不含列表的巢狀物件
如果是不含列表的巢狀物件,則使用帶 Class cls 入參的方法:
@Test public void testParseSimpleNestedJson() { String json = "{\"goods\":{\"desc\":\"2箱*250g\",\"goodsId\":8866,\"orderNo\":\"E20210522120237009258\",\"shopId\":659494,\"title\":\"認養一頭牛\"},\"order\":{\"bookTime\":1621656157,\"codPay\":false,\"deliveryType\":\"express\",\"userId\":1476}}"; BookInfo bookInfo = JsonUtil.toObject(json,BookInfo.class); Assert.assertNotNull(bookInfo); } @Test public void testParseSimpleNestedJson() { String json = "{\"goods\":{\"desc\":\"2箱*250g\",\"userId\":1476}}"; BookInfo bookInfo = GsonUtil.fromJson(json,BookInfo.class); Assert.assertNotNull(bookInfo); }
讀者可以自行解析出 BookInfo 的物件模型。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。