Skip to content

Commit 9cecd3c

Browse files
committed
Migrate JSON serialization to Jackson
Replaces Gson with Jackson for JSON serialization in Zest core classes. * Introducing custom configuration for type handling, date formatting, and HTML-safe escaping * Adds a new custom Jackson serializer modifier for Zest-specific serialization needs * Updates dependencies * Adapts tests and constructors for Jackson This improves extensibility, consistency, and future maintainability of serialization logic. Signed-off-by: Umoxfo <[email protected]>
1 parent 41c2899 commit 9cecd3c

File tree

12 files changed

+448
-119
lines changed

12 files changed

+448
-119
lines changed

build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ sourceSets.test.resources.srcDirs 'examples'
2828
dependencies {
2929
implementation (
3030
'org.apache.httpcomponents:httpclient:4.5.8',
31-
'com.google.code.gson:gson:2.8.5',
3231
'org.seleniumhq.selenium:selenium-java:4.38.0',
3332
'org.seleniumhq.selenium:htmlunit3-driver:4.34.0',
3433
'net.htmlparser.jericho:jericho-html:3.1')
3534

36-
implementation(platform("com.fasterxml.jackson:jackson-bom:2.17.0"))
35+
implementation(platform("com.fasterxml.jackson:jackson-bom:2.20.1"))
3736
implementation("com.fasterxml.jackson.core:jackson-core")
3837
implementation("com.fasterxml.jackson.core:jackson-databind")
38+
implementation("com.fasterxml.jackson.core:jackson-annotations")
3939
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml")
4040

41+
implementation("io.github.classgraph:classgraph:4.8.184")
42+
4143
testImplementation("org.wiremock:wiremock:3.10.0")
4244
testImplementation("org.mockito:mockito-core:5.15.2")
4345
testImplementation("org.nanohttpd:nanohttpd-webserver:2.3.1")

src/main/java/org/zaproxy/zest/core/v1/ZestCookie.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44
package org.zaproxy.zest.core.v1;
55

6+
import com.fasterxml.jackson.annotation.JsonCreator;
7+
import com.fasterxml.jackson.annotation.JsonGetter;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonSetter;
610
import java.util.Date;
711
import java.util.Objects;
812

@@ -18,8 +22,14 @@ public class ZestCookie {
1822
private Date expiry;
1923
private boolean secure;
2024

25+
@JsonCreator
2126
public ZestCookie(
22-
String domain, String name, String value, String path, Date expiry, boolean secure) {
27+
@JsonProperty("domain") String domain,
28+
@JsonProperty("name") String name,
29+
@JsonProperty("value") String value,
30+
@JsonProperty("path") String path,
31+
@JsonProperty("expiry") Date expiry,
32+
@JsonProperty("secure") boolean secure) {
2333
this.domain = domain;
2434
this.name = name;
2535
this.value = value;
@@ -60,10 +70,12 @@ public void setPath(String path) {
6070
this.path = path;
6171
}
6272

73+
@JsonGetter("expiry")
6374
public Date getExpiryDate() {
6475
return expiry;
6576
}
6677

78+
@JsonSetter("expiry")
6779
public void setExpiryDate(Date expiry) {
6880
this.expiry = expiry;
6981
}

src/main/java/org/zaproxy/zest/core/v1/ZestJSON.java

Lines changed: 66 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,25 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44
package org.zaproxy.zest.core.v1;
55

6-
import com.google.gson.Gson;
7-
import com.google.gson.GsonBuilder;
8-
import com.google.gson.JsonDeserializationContext;
9-
import com.google.gson.JsonDeserializer;
10-
import com.google.gson.JsonElement;
11-
import com.google.gson.JsonObject;
12-
import com.google.gson.JsonParseException;
13-
import com.google.gson.JsonPrimitive;
14-
import com.google.gson.JsonSerializationContext;
15-
import com.google.gson.JsonSerializer;
16-
import java.lang.reflect.Type;
17-
import java.time.Instant;
18-
import java.time.ZoneOffset;
19-
import java.time.format.DateTimeFormatter;
20-
import java.time.format.DateTimeParseException;
21-
import java.util.Arrays;
22-
import java.util.Date;
6+
import com.fasterxml.jackson.core.JsonFactory;
7+
import com.fasterxml.jackson.core.JsonFactoryBuilder;
8+
import com.fasterxml.jackson.core.JsonProcessingException;
9+
import com.fasterxml.jackson.core.PrettyPrinter;
10+
import com.fasterxml.jackson.core.util.DefaultIndenter;
11+
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
12+
import com.fasterxml.jackson.core.util.Separators;
13+
import com.fasterxml.jackson.databind.SerializationFeature;
14+
import com.fasterxml.jackson.databind.json.JsonMapper;
15+
import org.zaproxy.zest.impl.jackson.HtmlSafeCharacterEscapes;
16+
import org.zaproxy.zest.impl.jackson.JacksonConfig;
2317

2418
// TODO: Auto-generated Javadoc
19+
2520
/** The Class ZestJSON. */
2621
public class ZestJSON {
2722

28-
/** The gson. */
29-
private static Gson gson = null;
23+
/** The object mapper. */
24+
private static JsonMapper jsonMapper = null;
3025

3126
/**
3227
* To string.
@@ -35,7 +30,12 @@ public class ZestJSON {
3530
* @return the string
3631
*/
3732
public static String toString(ZestElement element) {
38-
return getGson().toJson(element);
33+
try {
34+
return getJsonMapper().writeValueAsString(element);
35+
} catch (JsonProcessingException e) {
36+
// GSON.toJson doesn't throw checked exceptions, maintain similar behavior
37+
throw new RuntimeException("Failed to serialize ZestElement", e);
38+
}
3939
}
4040

4141
/**
@@ -45,97 +45,61 @@ public static String toString(ZestElement element) {
4545
* @return the zest element
4646
*/
4747
public static ZestElement fromString(String str) {
48-
ZestElement ze = getGson().fromJson(str, ZestElement.class);
49-
if (ze != null && ze instanceof ZestStatement) {
50-
((ZestStatement) ze).init();
48+
ZestElement ze;
49+
try {
50+
ze = getJsonMapper().readValue(str, ZestElement.class);
51+
} catch (JsonProcessingException e) {
52+
// GSON.fromJson throws JsonParseException (RuntimeException)
53+
throw new RuntimeException("Failed to deserialize ZestElement", e);
5154
}
52-
return ze;
53-
}
5455

55-
/**
56-
* Gets the gson.
57-
*
58-
* @return the gson
59-
*/
60-
private static Gson getGson() {
61-
if (gson == null) {
62-
GsonBuilder builder = newDefaultGsonBuilder();
63-
// Need to add all of the abstract classes
64-
Arrays.asList(
65-
ZestAction.class,
66-
ZestAssignment.class,
67-
ZestAuthentication.class,
68-
ZestElement.class,
69-
ZestStatement.class,
70-
ZestExpressionElement.class,
71-
ZestLoop.class,
72-
ZestLoopState.class,
73-
ZestLoopTokenSet.class)
74-
.forEach(type -> builder.registerTypeAdapter(type, ZestTypeAdapter.INSTANCE));
75-
gson = builder.create();
56+
if (ze instanceof ZestStatement zs) {
57+
zs.init();
7658
}
77-
return gson;
78-
}
7959

80-
private static GsonBuilder newDefaultGsonBuilder() {
81-
return new GsonBuilder()
82-
.registerTypeAdapter(Date.class, DateTypeAdapter.INSTANCE)
83-
.setPrettyPrinting();
60+
return ze;
8461
}
8562

86-
private static class ZestTypeAdapter
87-
implements JsonDeserializer<ZestElement>, JsonSerializer<ZestElement> {
88-
89-
static final ZestTypeAdapter INSTANCE = new ZestTypeAdapter();
90-
91-
private static final String ZEST_PACKAGE = ZestTypeAdapter.class.getPackage().getName();
92-
93-
@Override
94-
public ZestElement deserialize(
95-
JsonElement element, Type rawType, JsonDeserializationContext arg2) {
96-
if (element instanceof JsonObject) {
97-
String elementType = ((JsonObject) element).get("elementType").getAsString();
98-
99-
if (elementType.startsWith("Zest")) {
100-
try {
101-
Class<?> c = Class.forName(ZEST_PACKAGE + "." + elementType);
102-
return (ZestElement) getGson().fromJson(element, c);
103-
104-
} catch (ClassNotFoundException e) {
105-
throw new JsonParseException(e);
106-
}
107-
}
108-
}
109-
return null;
63+
private static JsonMapper getJsonMapper() {
64+
if (jsonMapper == null) {
65+
JsonFactory factory =
66+
((JsonFactoryBuilder) JsonFactory.builder())
67+
.characterEscapes(HtmlSafeCharacterEscapes.instance())
68+
.build();
69+
70+
JsonMapper.Builder builder =
71+
JsonMapper.builder(factory)
72+
.defaultPrettyPrinter(createPrettyPrinter())
73+
// Equivalent to setPrettyPrinting()
74+
.enable(SerializationFeature.INDENT_OUTPUT);
75+
76+
// Apply common Zest configuration and build
77+
jsonMapper = JacksonConfig.configureCommonBuilder(builder).build();
11078
}
11179

112-
@Override
113-
public JsonElement serialize(
114-
ZestElement element, Type rawType, JsonSerializationContext context) {
115-
return newDefaultGsonBuilder().create().toJsonTree(element);
116-
}
80+
return jsonMapper;
11781
}
11882

119-
private static class DateTypeAdapter implements JsonDeserializer<Date>, JsonSerializer<Date> {
120-
121-
static final DateTypeAdapter INSTANCE = new DateTypeAdapter();
122-
123-
private static final DateTimeFormatter FORMAT = DateTimeFormatter.ISO_DATE_TIME;
124-
125-
@Override
126-
public JsonElement serialize(Date src, Type typeOfSrc, JsonSerializationContext context) {
127-
return new JsonPrimitive(FORMAT.format(src.toInstant().atOffset(ZoneOffset.UTC)));
128-
}
129-
130-
@Override
131-
public Date deserialize(
132-
JsonElement json, Type typeOfT, JsonDeserializationContext context) {
133-
String value = json.getAsJsonPrimitive().getAsString();
134-
try {
135-
return new Date(FORMAT.parse(value, Instant::from).toEpochMilli());
136-
} catch (DateTimeParseException e) {
137-
throw new JsonParseException("Failed to parse the date: " + value, e);
138-
}
139-
}
83+
public static DefaultPrettyPrinter createPrettyPrinter() {
84+
Separators separator = PrettyPrinter.DEFAULT_SEPARATORS;
85+
separator =
86+
new Separators(
87+
Separators.DEFAULT_ROOT_VALUE_SEPARATOR,
88+
separator.getObjectFieldValueSeparator(),
89+
Separators.Spacing.AFTER,
90+
separator.getObjectEntrySeparator(),
91+
Separators.Spacing.NONE,
92+
"",
93+
separator.getArrayValueSeparator(),
94+
Separators.Spacing.NONE,
95+
"");
96+
97+
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separator);
98+
99+
var indenter = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withLinefeed("\n");
100+
prettyPrinter.indentArraysWith(indenter);
101+
prettyPrinter.indentObjectsWith(indenter);
102+
103+
return prettyPrinter;
140104
}
141105
}

src/main/java/org/zaproxy/zest/core/v1/ZestLoopTokenFileSet.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44
package org.zaproxy.zest.core.v1;
55

6+
import com.fasterxml.jackson.annotation.JsonCreator;
7+
import com.fasterxml.jackson.annotation.JsonGetter;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
69
import java.io.File;
710
import java.io.FileNotFoundException;
811
import java.util.Collections;
@@ -28,7 +31,9 @@ public class ZestLoopTokenFileSet extends ZestElement implements ZestLoopTokenSe
2831
* @param pathToFile the path to file
2932
* @throws FileNotFoundException the file not found exception
3033
*/
31-
public ZestLoopTokenFileSet(String pathToFile) throws FileNotFoundException {
34+
@JsonCreator
35+
public ZestLoopTokenFileSet(@JsonProperty("pathToFile") String pathToFile)
36+
throws FileNotFoundException {
3237
super();
3338
this.pathToFile = pathToFile;
3439
this.convertedSet = this.getConvertedSet(new File(pathToFile));
@@ -105,6 +110,7 @@ public File getFile() {
105110
return new File(pathToFile);
106111
}
107112

113+
@JsonGetter("pathToFile")
108114
public String getFilePath() {
109115
return this.pathToFile;
110116
}

src/main/java/org/zaproxy/zest/core/v1/ZestResponse.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44
package org.zaproxy.zest.core.v1;
55

6+
import com.fasterxml.jackson.annotation.JsonCreator;
7+
import com.fasterxml.jackson.annotation.JsonProperty;
68
import java.net.URL;
79

810
/** The Class ZestResponse. */
@@ -32,8 +34,13 @@ public class ZestResponse extends ZestElement {
3234
* @param statusCode the status code
3335
* @param responseTimeInMs the response time in ms
3436
*/
37+
@JsonCreator
3538
public ZestResponse(
36-
URL url, String headers, String body, int statusCode, long responseTimeInMs) {
39+
@JsonProperty("url") URL url,
40+
@JsonProperty("headers") String headers,
41+
@JsonProperty("body") String body,
42+
@JsonProperty("statusCode") int statusCode,
43+
@JsonProperty("responseTimeInMs") long responseTimeInMs) {
3744
this.url = url;
3845
this.headers = headers;
3946
this.body = body;

src/main/java/org/zaproxy/zest/core/v1/ZestStatement.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44
package org.zaproxy.zest.core.v1;
55

6+
import com.fasterxml.jackson.annotation.JsonIgnore;
67
import java.net.MalformedURLException;
78

89
/** The base abstract class that all Zest statements must extend. */
@@ -177,6 +178,7 @@ public void setEnabled(boolean enabled) {
177178
*
178179
* @return true, if is passive
179180
*/
181+
@JsonIgnore
180182
public abstract boolean isPassive();
181183

182184
/* Useful when debuging ;)

0 commit comments

Comments
 (0)