Skip to content

Commit b8bcc5f

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 b8bcc5f

File tree

12 files changed

+468
-118
lines changed

12 files changed

+468
-118
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: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
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;
10+
611
import java.util.Date;
712
import java.util.Objects;
813

@@ -18,8 +23,14 @@ public class ZestCookie {
1823
private Date expiry;
1924
private boolean secure;
2025

26+
@JsonCreator
2127
public ZestCookie(
22-
String domain, String name, String value, String path, Date expiry, boolean secure) {
28+
@JsonProperty("domain") String domain,
29+
@JsonProperty("name") String name,
30+
@JsonProperty("value") String value,
31+
@JsonProperty("path") String path,
32+
@JsonProperty("expiry") Date expiry,
33+
@JsonProperty("secure") boolean secure) {
2334
this.domain = domain;
2435
this.name = name;
2536
this.value = value;
@@ -60,10 +71,12 @@ public void setPath(String path) {
6071
this.path = path;
6172
}
6273

74+
@JsonGetter("expiry")
6375
public Date getExpiryDate() {
6476
return expiry;
6577
}
6678

79+
@JsonSetter("expiry")
6780
public void setExpiryDate(Date expiry) {
6881
this.expiry = expiry;
6982
}

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

Lines changed: 67 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,29 @@
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
25-
/** The Class ZestJSON. */
19+
20+
/**
21+
* The Class ZestJSON.
22+
*/
2623
public class ZestJSON {
2724

28-
/** The gson. */
29-
private static Gson gson = null;
25+
/**
26+
* The object mapper.
27+
*/
28+
private static JsonMapper jsonMapper = null;
3029

3130
/**
3231
* To string.
@@ -35,7 +34,12 @@ public class ZestJSON {
3534
* @return the string
3635
*/
3736
public static String toString(ZestElement element) {
38-
return getGson().toJson(element);
37+
try {
38+
return getJsonMapper().writeValueAsString(element);
39+
} catch (JsonProcessingException e) {
40+
// GSON.toJson doesn't throw checked exceptions, maintain similar behavior
41+
throw new RuntimeException("Failed to serialize ZestElement", e);
42+
}
3943
}
4044

4145
/**
@@ -45,97 +49,59 @@ public static String toString(ZestElement element) {
4549
* @return the zest element
4650
*/
4751
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();
52+
ZestElement ze;
53+
try {
54+
ze = getJsonMapper().readValue(str, ZestElement.class);
55+
} catch (JsonProcessingException e) {
56+
// GSON.fromJson throws JsonParseException (RuntimeException)
57+
throw new RuntimeException("Failed to deserialize ZestElement", e);
5158
}
52-
return ze;
53-
}
5459

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();
60+
if (ze instanceof ZestStatement zs) {
61+
zs.init();
7662
}
77-
return gson;
78-
}
7963

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

86-
private static class ZestTypeAdapter
87-
implements JsonDeserializer<ZestElement>, JsonSerializer<ZestElement> {
67+
private static JsonMapper getJsonMapper() {
68+
if (jsonMapper == null) {
69+
JsonFactory factory = ((JsonFactoryBuilder) JsonFactory.builder())
70+
.characterEscapes(HtmlSafeCharacterEscapes.instance())
71+
.build();
8872

89-
static final ZestTypeAdapter INSTANCE = new ZestTypeAdapter();
73+
JsonMapper.Builder builder = JsonMapper.builder(factory)
74+
.defaultPrettyPrinter(createPrettyPrinter())
75+
// Equivalent to setPrettyPrinting()
76+
.enable(SerializationFeature.INDENT_OUTPUT);
9077

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;
78+
// Apply common Zest configuration and build
79+
jsonMapper = JacksonConfig.configureCommonBuilder(builder).build();
11080
}
11181

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

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-
}
85+
public static DefaultPrettyPrinter createPrettyPrinter() {
86+
Separators separator = PrettyPrinter.DEFAULT_SEPARATORS;
87+
separator = new Separators(
88+
Separators.DEFAULT_ROOT_VALUE_SEPARATOR,
89+
separator.getObjectFieldValueSeparator(),
90+
Separators.Spacing.AFTER,
91+
separator.getObjectEntrySeparator(),
92+
Separators.Spacing.NONE,
93+
"",
94+
separator.getArrayValueSeparator(),
95+
Separators.Spacing.NONE,
96+
""
97+
);
98+
99+
DefaultPrettyPrinter prettyPrinter = new DefaultPrettyPrinter(separator);
100+
101+
var indenter = DefaultIndenter.SYSTEM_LINEFEED_INSTANCE.withLinefeed("\n");
102+
prettyPrinter.indentArraysWith(indenter);
103+
prettyPrinter.indentObjectsWith(indenter);
104+
105+
return prettyPrinter;
140106
}
141107
}

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,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+
610
import java.io.File;
711
import java.io.FileNotFoundException;
812
import java.util.Collections;
@@ -28,7 +32,8 @@ public class ZestLoopTokenFileSet extends ZestElement implements ZestLoopTokenSe
2832
* @param pathToFile the path to file
2933
* @throws FileNotFoundException the file not found exception
3034
*/
31-
public ZestLoopTokenFileSet(String pathToFile) throws FileNotFoundException {
35+
@JsonCreator
36+
public ZestLoopTokenFileSet(@JsonProperty("pathToFile") String pathToFile) 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: 9 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.JsonProperty;
8+
69
import java.net.URL;
710

811
/** The Class ZestResponse. */
@@ -32,8 +35,13 @@ public class ZestResponse extends ZestElement {
3235
* @param statusCode the status code
3336
* @param responseTimeInMs the response time in ms
3437
*/
38+
@JsonCreator
3539
public ZestResponse(
36-
URL url, String headers, String body, int statusCode, long responseTimeInMs) {
40+
@JsonProperty("url") URL url,
41+
@JsonProperty("headers") String headers,
42+
@JsonProperty("body") String body,
43+
@JsonProperty("statusCode") int statusCode,
44+
@JsonProperty("responseTimeInMs") long responseTimeInMs) {
3745
this.url = url;
3846
this.headers = headers;
3947
this.body = body;

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

Lines changed: 3 additions & 0 deletions
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.JsonIgnore;
7+
68
import java.net.MalformedURLException;
79

810
/** The base abstract class that all Zest statements must extend. */
@@ -177,6 +179,7 @@ public void setEnabled(boolean enabled) {
177179
*
178180
* @return true, if is passive
179181
*/
182+
@JsonIgnore
180183
public abstract boolean isPassive();
181184

182185
/* Useful when debuging ;)

0 commit comments

Comments
 (0)