Jackson:JSON parser biblioteka sa najviše zvezdica na GitHubu
U današnjem svetu programiranja, JSON je već postao preferirani protokol za prenos informacija sa klijenta na server, može se bez preterivanja reći, XML je onaj prethodni talas koji je udaren na plaži.
Nažalost, JDK nema JSON biblioteku, ne znam zašto nisu napravili. Kod Log4j, da bi se takmičili, još su uveli java.util.logging, iako na kraju nisu mnogo koristili.
Razlog zašto je Java sjajna je u velikoj meri zato što je njena ekologija veoma kompletna, JDK nema JSON biblioteku, treće strane biblioteke imaju, i prilično dobre, na primer glavni lik ovog članka – Jackson, na GitHubu označen sa 6.1k zvezdica, podrazumevani JSON parser u Spring Boot.
Kako dokazati ovu tačku?
Kada kroz starter kreiramo novi Spring Boot Web projekat, možemo viditi Jacksonovu prisutnost u Maven zavisnostima.

Jackson ima mnoge prednosti:
Brzina analize velikih fajlova;
Za vreme izvršavanja zauzima manje memorije, performanse su bolje;
API je veoma fleksibilan, lako se proširuje i prilagođuje.
Jesonove osnovne module se sastoje od tri dela:
jackson-core, osnovni paket, pruža API vezan za analizu u "tok režimu", uključuje JsonPaser i JsonGenerator.
jackson-annotations, paket anotacija, pruža standardne funkcije anotacija;
jackson-databind, paket povezivanja podataka, pruža API vezan za analizu "povezivanja objekata" (ObjectMapper) i API vezan za analizu "modela stabla" (JsonNode).
01, Uvođenje Jackson zavisnosti
Da biste koristili Jackson, treba dodati Jackson zavisnost u fajl pom.xml.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.1</version>
</dependency>jackson-databind zavisi od jackson-core i jackson-annotations, zato nakon dodavanja jackson-databind, Maven će automatski uvesti jackson-core i jackson-annotations u projekat.

Razlog zašto Maven voli ljudi je u tome što može tajno pomoći da se uradi što treba.
02, Korišćenje ObjectMapper
Jackson najčešće korišćeni API je ObjectMapper baziran na "povezivanje objekata", kroz seriju metoda writeValue pretvara Java objekte u JSON, i može čuvati u različitim formatima.
Metod
writeValueAsString(Object value), čuva objekat kao stringMetod
writeValueAsBytes(Object value), čuva objekat kao niz bajtovaMetod
writeValue(File resultFile, Object value), čuva objekat kao fajl
Hajde da vidimo primer koda čuvanja kao string:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* WeChat pretraga "Chenmo Wang Er", odgovorite Java
*
* @author Chenmo Wang Er
* @date 2020/11/26
*/
public class Demo {
public static void main(String[] args) throws JsonProcessingException {
Writer wanger = new Writer("Chenmo Wang Er", 18);
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(wanger);
System.out.println(jsonString);
}
}
class Writer {
private String name;
private int age;
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}Rezultati izlaza programa su kao sledeći:
{
"name" : "Chenmo Wang Er",
"age" : 18
}Nije sva polja podržavaju serijalizaciju i deserializaciju, treba zadovoljiti sledeća pravila:
Ako je modifikator polja public, onda se to polje može serijalizovati i deserializovati (nije standardni način pisanja).
Ako modifikator polja nije public, ali njegove getter i setter metode su public, onda se to polje može serijalizovati i deserializovati. Getter metoda se koristi za serijalizaciju, setter metoda se koristi za deserializaciju.
Ako polje ima samo public setter metodu, a nema public getter metodu, onda se to polje može koristiti samo za deserializaciju.
Ako želite promeniti podrazumevana pravila serijalizacije i deserializacije, treba pozvati metod setVisibility() ObjectMapper-a. Inače će baciti izuzetak InvalidDefinitionException.
ObjectMapper kroz seriju metoda readValue iz različitih izvora podataka deserializuje JSON u Java objekte.
Metod
readValue(String content, Class<T> valueType), deserializuje string u Java objekatMetod
readValue(byte[] src, Class<T> valueType), deserializuje niz bajtova u Java objekatMetod
readValue(File src, Class<T> valueType), deserializuje fajl u Java objekat
Hajde da vidimo primer koda deserializacije stringa u Java objekat:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* WeChat pretraga "Chenmo Wang Er", odgovorite Java
*
* @author Chenmo Wang Er
* @date 2020/11/26
*/
public class Demo {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String jsonString = "{\n" +
" \"name\" : \"Chenmo Wang Er\",\n" +
" \"age\" : 18\n" +
"}";
Writer deserializedWriter = mapper.readValue(jsonString, Writer.class);
System.out.println(deserializedWriter);
}
}
class Writer{
private String name;
private int age;
// getter/setter
@Override
public String toString() {
return "Writer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}Rezultati izlaza programa su kao sledeći:
Writer{name='Chenmo Wang Er', age=18}PS: ako objekat deserializacije ima konstruktor sa parametrima, mora imati prazan podrazumevani konstruktor, inače će baciti InvalidDefinitionException u jednom redu.
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.itwanger.jackson.Writer` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (String)"{
"name" : "Chenmo Wang Er",
"age" : 18
}"; line: 2, column: 3]
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1589)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1055)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1297)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:326)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4202)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3205)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3173)
at com.itwanger.jackson.Demo.main(Demo.java:19)Jackson najčešće korišćeni API je ObjectMapper baziran na "povezivanje objekata",
ObjectMapper takođe može analizirati JSON u objekte JsonNode bazirane na "modelu stabla", hajde da vidimo sledeći primer.
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* WeChat pretraga "Chenmo Wang Er", odgovorite Java
*
* @author Chenmo Wang Er
* @date 2020/11/26
*/
public class JsonNodeDemo {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = "{ \"name\" : \"Chenmo Wang Er\", \"age\" : 18 }";
JsonNode jsonNode = mapper.readTree(json);
String name = jsonNode.get("name").asText();
System.out.println(name); // Chenmo Wang Er
}
}Uz pomoć TypeReference može pretvoriti JSON string niz u generičku Listu, hajde da vidimo sledeći primer:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.List;
/**
* WeChat pretraga "Chenmo Wang Er", odgovorite Java
*
* @author Chenmo Wang Er
* @date 2020/11/26
*/
public class TypeReferenceDemo {
public static void main(String[] args) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
String json = "[{ \"name\" : \"Chenmo Wang San\", \"age\" : 18 }, { \"name\" : \"Chenmo Wang Er\", \"age\" : 19 }]";
List<Author> listAuthor = mapper.readValue(json, new TypeReference<List<Author>>(){});
System.out.println(listAuthor);
}
}
class Author{
private String name;
private int age;
// getter/setter
// toString
}03, Naprednija konfiguracija
Vrlo važan razlog zašto je Jackson sjajan je što može realizovati visoko fleksibilnu prilagođenu konfiguraciju.
U stvarnom scenariju primene, JSON često ima polja koja ne postoje u Java objektu, u ovom trenutku, ako direktno analizirate, će baciti izuzetak UnrecognizedPropertyException.
Sledeći JSON string:
String jsonString = "{\n" +
" \"name\" : \"Chenmo Wang Er\",\n" +
" \"age\" : 18\n" +
" \"sex\" : "muški",\n" +
"}";Ali u Java objektu Writer nije definisano polje sex:
class Writer{
private String name;
private int age;
// getter/setter
}Pokušajmo da analiziramo:
ObjectMapper mapper = new ObjectMapper();
Writer deserializedWriter = mapper.readValue(jsonString, Writer.class);Iznenađenje, baceno je izuzetak, sex se ne može prepoznati.
Exception in thread "main" com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "sex" (class com.itwanger.jackson.Writer), not marked as ignorable (2 known properties: "name", "age"])
at [Source: (String)"{
"name" : "Chenmo Wang Er",
"age" : 18,
"sex" : "muški"
}"; line: 4, column: 12] (through reference chain: com.itwanger.jackson.Writer["sex"])Šta raditi? Možete kroz metod configure() ignorisati ova "neprepoznata" polja.
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);Osim toga, postoje i druge korisne konfiguracione informacije, hajde da upoznamo:
// U vreme serijalizacije ignorišite osobine sa null vrednošću
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
// Ignorišite osobine sa podrazumevanom vrednošću
mapper.setDefaultPropertyInclusion(JsonInclude.Include.NON_DEFAULT);04, Obrada formata datuma
Za polja tipa datuma, na primer java.util.Date, ako ne specificirate format, nakon serijalizacije će se prikazati kao podaci tipa long, čitljivost ovog podrazumevanog formata je vrlo loša.
{
"age" : 18,
"birthday" : 1606358621209
}Šta raditi?
Prvo rešenje, koristite anotaciju @JsonFormat na getteru.
private Date birthday;
// GMT+8 označava standardno vreme Griniča, plus osam sati predstavlja vreme u vašoj trenutnoj vremenskoj zoni
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}Hajde da vidimo rezultat:
{
"age" : 18,
"birthday" : "2020-11-26 03:02:30"
}Konkretan kod je kao sledeći:
ObjectMapper mapper = new ObjectMapper();
Writer wanger = new Writer("Chenmo Wang Er", 18);
wanger.setBirthday(new Date());
String jsonString = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(wanger);
System.out.println(jsonString);Drugo rešenje, pozovite metod setDateFormat() ObjectMapper-a.
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(StdDateFormat.getDateTimeInstance());
Writer wanger = new Writer("Chenmo Wang Er", 18);
wanger.setBirthday(new Date());
String jsonString = mapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(wanger);
System.out.println(jsonString);Rezultati izlaza su kao sledeći:
{
"name" : "Chenmo Wang Er",
"age" : 18,
"birthday" : "2020.11.26 ujutru 11:09:51"
}05, Filtriranje polja
Kada serijalizujemo Java objekte u JSON, možda neka polja treba filtrirati, ne prikazivati u JSON-u, Jackson ima jednu relativno jednostavnu realizaciju.
@JsonIgnore se koristi za filtriranje jednog polja.
@JsonIgnore
public String getName() {
return name;
}@JsonIgnoreProperties se koristi za filtriranje više polja.
@JsonIgnoreProperties(value = { "age","birthday" })
class Writer{
private String name;
private int age;
private Date birthday;
}06, Prilagođena serijalizacija i deserializacija
Kada Jackson podrazumevana serijalizacija i deserializacija ne mogu zadovoljiti stvarne razvojne potrebe, možete prilagođeno kreirati nove klase serijalizacije i deserializacije.
Prilagođena klasa serijalizacije treba da nasledi StdSerializer, istovremeno revidirati metod serialize(), koristeći JsonGenerator da generiše JSON, primer je kao sledeći:
/**
* WeChat pretraga "Chenmo Wang Er", odgovorite Java
*
* @author Chenmo Wang Er
* @date 2020/11/26
*/
public class CustomSerializer extends StdSerializer<Man> {
protected CustomSerializer(Class<Man> t) {
super(t);
}
public CustomSerializer() {
this(null);
}
@Override
public void serialize(Man value, JsonGenerator gen, SerializerProvider provider) throws IOException {
gen.writeStartObject();
gen.writeStringField("name", value.getName());
gen.writeEndObject();
}
}
class Man{
private int age;
private String name;
public Man(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}Nakon što se definira prilagođena klasa serijalizacije, ako želite da ih pozovete u programu, treba da ih registrujete u Modulu ObjectMapper-a, primer je kao sledeći:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomSerializer", new Version(1, 0, 0, null, null, null));
module.addSerializer(Man.class, new CustomSerializer());
mapper.registerModule(module);
Man man = new Man( 18,"Chenmo Wang Er");
String json = mapper.writeValueAsString(man);
System.out.println(json);Rezultati izlaza programa su kao sledeći:
{"name":"Chenmo Wang Er"}U prilagođenoj klasi serijalizacije CustomSerializer nije dodato polje age, zato se izbacuje samo polje name.
Hajde da vidimo prilagođenu klasu deserializacije, nasledi StdDeserializer, istovremeno revidirati metod deserialize(), koristeći JsonGenerator da čita JSON, primer je kao sledeći:
public class CustomDeserializer extends StdDeserializer<Woman> {
protected CustomDeserializer(Class<?> vc) {
super(vc);
}
public CustomDeserializer() {
this(null);
}
@Override
public Woman deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode node = p.getCodec().readTree(p);
Woman woman = new Woman();
int age = (Integer) ((IntNode) node.get("age")).numberValue();
String name = node.get("name").asText();
woman.setAge(age);
woman.setName(name);
return woman;
}
}
class Woman{
private int age;
private String name;
public Woman() {
}
// getter/setter
@Override
public String toString() {
return "Woman{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}Kroz JsonNode čita JSON u strukturu stabla, zatim kroz get metod JsonNode-a čita odgovarajuća polja, zatim generiše nove Java objekte i vraca.
Nakon što se definira prilagođena klasa deserializacije, ako želite da ih pozovete u programu, takođe treba da ih registrujete u Modulu ObjectMapper-a, primer je kao sledeći:
ObjectMapper mapper = new ObjectMapper();
SimpleModule module =
new SimpleModule("CustomDeserializer", new Version(1, 0, 0, null, null, null));
module.addDeserializer(Woman.class, new CustomDeserializer());
mapper.registerModule(module);
String json = "{ \"name\" : \"Sanmej\", \"age\" : 18 }";
Woman woman = mapper.readValue(json, Woman.class);
System.out.println(woman);Rezultati izlaza programa su kao sledeći:
Woman{age=18, name='Sanmej'}07, Zaključak
Evo, izgleda lepo, Jackson apsolutno zaslužuje reči "najbolji", iako je malo prazan. Ako samo želite jednostavnu serijalizaciju i deserializaciju, koristite write i read metode ObjectMapper-a.
Ako želite još dalje, treba da prilagodite neke konfiguracije ObjectMapper-a, ili dodate neke anotacije, i direktno prilagođene klase serijalizacije i deserializacije, bliže Java objektima.
Treba obratiti pažnju na polja formata datuma, pokušajte da ne koristite podrazumevanu konfiguraciju, čitljivost je vrlo loša.
Dobro, kroz sistematsko uvođenje ovog članka, verujem da ste potpuno razumeli Jackson, vidimo se u sledećem članku.
