33 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
44package 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. */
2621public 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}
0 commit comments