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
25- /** The Class ZestJSON. */
19+
20+ /**
21+ * The Class ZestJSON.
22+ */
2623public 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}
0 commit comments