سالها با Minimal-Json که حاصل تلاش Ralf Sternberg کار کردم یک کتابخانه ساده ، سریع و البته خوش کدی است که میتوانید ازش بسادگی استفاده کنید و حتی پیچیده ترین ساختار های Json را هم parse کنید و استفاده کنید
ولی وقتی بخواهید یک تناظر با Object داشته باشید و یکمی حرفه ای تر کار کنید نیاز داریم که کتابخانه های دیگری را استفاده کنیم
معمولا در کد هایی که زیاد از این Parser استفاده میشود مسئله پرفرمانس بسیار مهم خواهد بود و اصولا باید benchmark هایی را دید و اگر موجود نیست خودمان بین انتخاب هایمان بنچمارک کنیم
مسئله اول Performance !!
خوشبختانه بنچمارک بروزی در این ریپو قرار دارد که میتوان از روی آن انتخاب کرد
https://github.com/fabienrenaud/java-json-benchmark
همانطور که میبینید dsl-json با اختلاف زیادی از کتابخانه های دیگری که در شرکت های بزرگی همچون گوگل دولوپ شده سریع تر است
قبل از ادامه باید این نکته را ذکر کرد که کتابخانه ای که میخواهیم باید اول باید فیچر های کلیدی که ما حتما به آنها نیاز داریم را مشخص کنیم تا در وقت صرفه جویی شود
فیچر هایی که من نیاز داشتم این گزینه ها بود :
موقع serialization :
- فیلد هایی اجباری را مشخص کنم که مقدارش اگر null بود با خطا روبرو شوم
- فیلدهایی را بتوانم با مقدار null داشته باشم و در این حالت این فیلد ها ignore شوند
موقع deserialization :
- توانایی Object Mapping داشته باشد
- توانایی Simple Parser داشته باشد (گاهی پیش میاید که پیام دریافتی ممکن است چند حالت داشته باشد و از روی یک فیلدی میخواهید متوجه شوید که باید با کدامیک از Object های متناظر map شود )
- اگر فیلدی که نباید null باشد در پیام دریافتی json اگر غایب بود و یا null بود عملیات با خطا روبرو شود
خب حالا بریم سراغ کتابخانه ها
dsl-json به آدرس :
https://github.com/ngs-doo/dsl-json
این کتابخانه همه اون امکانات را با استفاده از settings تعریف شده دارد ولی simple parser را ندارد و همیشه باید از طریق Object متناظر مقادیر را دریافت کنیم
jsoniter به آدرس :
https://github.com/json-iterator/java
این کتابخانه همه موارد خواسته شده را با config تعریف شده دارد و simple parser هم قابل استفاده است
jackson به آدرس :
https://github.com/FasterXML/jackson
https://github.com/FasterXML/jackson-databind
https://github.com/FasterXML/jackson-annotations
جکسون کتابخانه نسبتا سنگینی است ولی ویژگی های بسیار حرفه ای تری را دارا میباشد و برای موارد خیلی خاص بد نیست نگاهی به این کتابخانه اول بیاندازید و بدون تعریف کردن settings یا config خاصی و تنها با استفاده از annotation های مخصوص میتوان not null بودن و یا اجباری بودن فیلد برای داشتن مقدار را تعریف کنیم
تست :
کتابخانه ها را از مخزن maven دریافت کنید
کلاس POJO را تعریف میکنیم :
package test; import com.dslplatform.json.CompiledJson; import com.dslplatform.json.JsonAttribute; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.jsoniter.annotation.JsonIgnore; @CompiledJson(onUnknown = CompiledJson.Behavior.IGNORE) @JsonInclude(JsonInclude.Include.NON_NULL) public class POJO { @JsonAttribute(nullable = false, name = "number") @JsonProperty(required = true) // @JsonInclude(JsonInclude.Include.NON_EMPTY) public Integer number; // @JsonAttribute(nullable = false, name = "name",mandatory = true) // @JsonInclude(JsonInclude.Include.NON_NULL) // @JsonProperty(required = true) // @com.jsoniter.annotation.JsonProperty(nullable = true,required = true) public String name; // @JsonInclude(JsonInclude.Include.NON_NULL) public String detail; @Override public String toString() { return "POJO{" + "number=" + number + ", name=" + name + ", detail=" + detail + '}'; } }
کلاس تست :
package test; import com.dslplatform.json.DslJson; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.jsoniter.JsonIterator; import com.jsoniter.any.Any; import com.jsoniter.output.JsonStream; import com.jsoniter.spi.Config; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.logging.Level; import java.util.logging.Logger; public class JsonTest { static String jsonStr1 = "{\"number\": 3,\"name\":\"سام\",\"detail\":\"detail for sam\"}"; static String jsonStr2 = "{\"number\": 3,\"name\":null,\"detail\":\"detail for sam\"}"; static String jsonStr3 = "{\"number\": 3,\"detail\":\"detail for sam\"}"; public final static ObjectMapper jsonMapper; static { jsonMapper = new ObjectMapper(); } public static void p(Object o) { if (o != null) { System.out.println(o.toString()); } } public static void p(Object... o) { for (Object oo : o) { p(oo); } } public static void jacksonSerialize() { p("================== Jackson Serialize =================="); try { POJO po = new POJO(); po.number = 1; po.name = "name1"; po.detail = "detail1"; String json1 = jsonMapper.writeValueAsString(po); p("json1:", json1); po.number = 2; po.name = null; po.detail = "detail1"; String json2 = jsonMapper.writeValueAsString(po); p("json2:", json2); } catch (JsonProcessingException ex) { Logger.getLogger(JsonTest.class.getName()).log(Level.SEVERE, null, ex); } } public static void jacksonDeserialize() { p("================== Jackson Deserialize =================="); try { JsonNode readTree1 = jsonMapper.readTree(jsonStr1); p("readTree 1:", readTree1.get("name")); JsonNode readTree2 = jsonMapper.readTree(jsonStr2); p("readTree 2:", readTree2.get("name")); JsonNode readTree3 = jsonMapper.readTree(jsonStr3); p("readTree 3:", readTree3.get("name")); POJO po1 = jsonMapper.readValue(jsonStr1, POJO.class); p("POJO 1:", po1); POJO po2 = jsonMapper.readValue(jsonStr2, POJO.class); p("POJO 2:", po2); POJO po3 = jsonMapper.readValue(jsonStr3, POJO.class); p("POJO 3:", po3); } catch (JsonProcessingException ex) { Logger.getLogger(JsonTest.class.getName()).log(Level.SEVERE, null, ex); } } public static void jsoniterSerialize() { p("================== Jsoniter Serialize =================="); POJO po = new POJO(); po.number = 1; po.name = "name1"; po.detail = "detail1"; Config config = new Config.Builder().omitDefaultValue(true).build(); String json1 = JsonStream.serialize(config, po); // String json1 = JsonStream.serialize(po); p("json1:", json1); po.number = 2; po.name = null; po.detail = "detail1"; String json2 = JsonStream.serialize(config, po); p("json2:", json2); } public static void jsoniterDeserialize() { p("================== Jsoniter Deserialize =================="); Any any1 = JsonIterator.deserialize(jsonStr1); p("any 1:", any1.get("name")); Any any2 = JsonIterator.deserialize(jsonStr2); p("any 2:", any2.get("name")); Any any3 = JsonIterator.deserialize(jsonStr3); p("any 3:", any3.get("name")); POJO po1 = JsonIterator.deserialize(jsonStr1, POJO.class); p("POJO 1:", po1); POJO po2 = JsonIterator.deserialize(jsonStr2, POJO.class); p("POJO 2:", po2); POJO po3 = JsonIterator.deserialize(jsonStr3, POJO.class); p("POJO 3:", po3); } public static void dslJsonSerialize() { try { p("================== DSL Json Serialize =================="); DslJson.Settings<Object> setting = new DslJson.Settings<>().includeServiceLoader(); setting.skipDefaultValues(true); DslJson<Object> dsl = new DslJson<>(setting); POJO po = new POJO(); po.number = 1; po.name = "name1"; po.detail = "detail1"; ByteArrayOutputStream os = new ByteArrayOutputStream(); dsl.serialize(po, os); String json1 = os.toString("UTF-8"); p("json 1: ", json1); po.number = 2; po.name = null; po.detail = "detail1"; os = new ByteArrayOutputStream(); dsl.serialize(po, os); String json2 = os.toString("UTF-8"); p("json2:", json2); } catch (IOException ex) { Logger.getLogger(JsonTest.class.getName()).log(Level.SEVERE, null, ex); } } public static void dslJsonDeserialize() { try { p("================== DSL Json Deserialize =================="); DslJson.Settings<Object> setting = new DslJson.Settings<>().includeServiceLoader(); setting.skipDefaultValues(true); DslJson<Object> dsl = new DslJson<>(setting); /* missing simple json parser */ byte[] bytes = jsonStr1.getBytes(StandardCharsets.UTF_8); POJO po1 = dsl.deserialize(POJO.class, bytes, bytes.length); p("POJO 1:", po1); bytes = jsonStr2.getBytes(StandardCharsets.UTF_8); POJO po2 = dsl.deserialize(POJO.class, bytes, bytes.length); p("POJO 2:", po2); bytes = jsonStr3.getBytes(StandardCharsets.UTF_8); POJO po3 = dsl.deserialize(POJO.class, bytes, bytes.length); p("POJO 3:", po3); } catch (IOException ex) { Logger.getLogger(JsonTest.class.getName()).log(Level.SEVERE, null, ex); } } public static void main(String[] args) { jacksonSerialize(); jsoniterSerialize(); dslJsonSerialize(); jacksonDeserialize(); jsoniterDeserialize(); dslJsonDeserialize(); } }
با توجه به ویژگی های کلیدی که داشتم و سرعت بیشتر و حجم کمتر اشغال رم کتابخانه jsoniter میتواند انتخاب خوبی باشد البته باید در نظر بگیریم که تعداد دلوپر و آپدیت های این کتابخانه قابل مقایسه با Jackson نیست