001/*
002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de)
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.benchmarking.common.util;
017
018import com.google.gson.*;
019import com.google.gson.reflect.TypeToken;
020
021import java.io.IOException;
022import java.lang.reflect.Type;
023import java.nio.file.Files;
024import java.nio.file.Path;
025import java.time.Instant;
026import java.time.format.DateTimeFormatter;
027import java.util.List;
028import java.util.Locale;
029import java.util.Map;
030
031/**
032 * Common JSON serialization utilities for benchmark results.
033 * Provides consistent formatting and serialization across all benchmark modules.
034 *
035 */
036public final class JsonSerializationHelper {
037
038    private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_INSTANT;
039
040    /**
041     * Gson instance configured for benchmark result serialization.
042     * Features:
043     * - Pretty printing
044     * - Smart number formatting (integers without .0)
045     * - ISO instant formatting
046     * - Special floating point value serialization
047     * - Thread-safe singleton
048     */
049    public static final Gson GSON = new GsonBuilder()
050            .setPrettyPrinting()
051            .serializeSpecialFloatingPointValues()
052            .registerTypeAdapter(Double.class, new DoubleSerializer())
053            .registerTypeAdapter(Instant.class, new InstantSerializer())
054            .create();
055
056    /**
057     * Compact Gson instance for minimal output (no pretty printing).
058     * Useful for reducing JSON size in performance-critical situations.
059     */
060    public static final Gson COMPACT_GSON = new GsonBuilder()
061            .serializeSpecialFloatingPointValues()
062            .registerTypeAdapter(Double.class, new DoubleSerializer())
063            .registerTypeAdapter(Instant.class, new InstantSerializer())
064            .create();
065
066    private JsonSerializationHelper() {
067        // Utility class
068    }
069
070    /**
071     * Writes an object to a JSON file.
072     *
073     * @param path the file path to write to
074     * @param object the object to serialize
075     * @throws IOException if an I/O error occurs
076     */
077    public static void writeJsonFile(Path path, Object object) throws IOException {
078        String json = GSON.toJson(object);
079        Files.createDirectories(path.getParent());
080        Files.writeString(path, json);
081    }
082
083    /**
084     * Reads a JSON file into an object.
085     *
086     * @param <T> the type to deserialize to
087     * @param path the file path to read from
088     * @param type the class of the type to deserialize
089     * @return the deserialized object
090     * @throws IOException if an I/O error occurs
091     */
092    public static <T> T readJsonFile(Path path, Class<T> type) throws IOException {
093        String json = Files.readString(path);
094        return GSON.fromJson(json, type);
095    }
096
097    /**
098     * Reads a JSON file into a generic type using TypeToken.
099     * Useful for deserializing collections and generic types.
100     *
101     * @param <T> the type to deserialize to
102     * @param path the file path to read from
103     * @param typeToken the TypeToken representing the generic type
104     * @return the deserialized object
105     * @throws IOException if an I/O error occurs
106     */
107    public static <T> T readJsonFile(Path path, TypeToken<T> typeToken) throws IOException {
108        String json = Files.readString(path);
109        return GSON.fromJson(json, typeToken.getType());
110    }
111
112    /**
113     * Serializes an object to JSON string using the default GSON instance.
114     *
115     * @param object the object to serialize
116     * @return the JSON string representation
117     */
118    public static String toJson(Object object) {
119        return GSON.toJson(object);
120    }
121
122    /**
123     * Serializes an object to compact JSON string (no pretty printing).
124     *
125     * @param object the object to serialize
126     * @return the compact JSON string representation
127     */
128    public static String toCompactJson(Object object) {
129        return COMPACT_GSON.toJson(object);
130    }
131
132    /**
133     * Deserializes a JSON string to an object.
134     *
135     * @param <T> the type to deserialize to
136     * @param json the JSON string
137     * @param type the class of the type to deserialize
138     * @return the deserialized object
139     */
140    public static <T> T fromJson(String json, Class<T> type) {
141        return GSON.fromJson(json, type);
142    }
143
144    /**
145     * Deserializes a JSON string to a generic type using TypeToken.
146     *
147     * @param <T> the type to deserialize to
148     * @param json the JSON string
149     * @param typeToken the TypeToken representing the generic type
150     * @return the deserialized object
151     */
152    public static <T> T fromJson(String json, TypeToken<T> typeToken) {
153        return GSON.fromJson(json, typeToken.getType());
154    }
155
156    /**
157     * Convenience method for deserializing JSON to Map&lt;String, Object&gt;.
158     *
159     * @param json the JSON string
160     * @return the deserialized map
161     */
162    public static Map<String, Object> jsonToMap(String json) {
163        Type type = new TypeToken<Map<String, Object>>() {
164        }.getType();
165        return GSON.fromJson(json, type);
166    }
167
168    /**
169     * Convenience method for deserializing JSON to List&lt;Map&lt;String, Object&gt;&gt;.
170     *
171     * @param json the JSON string
172     * @return the deserialized list of maps
173     */
174    public static List<Map<String, Object>> jsonToListOfMaps(String json) {
175        Type type = new TypeToken<List<Map<String, Object>>>() {
176        }.getType();
177        return GSON.fromJson(json, type);
178    }
179
180    /**
181     * Creates a JsonElement from an object using the default GSON instance.
182     *
183     * @param object the object to convert
184     * @return the JsonElement representation
185     */
186    public static JsonElement toJsonTree(Object object) {
187        return GSON.toJsonTree(object);
188    }
189
190    /**
191     * Formats a double value for display.
192     * Returns integer representation if the value is a whole number.
193     *
194     * @param value the value to format
195     * @return formatted string
196     */
197    public static String formatDouble(double value) {
198        if (value == (long) value) {
199            return String.valueOf((long) value);
200        }
201        return String.format(Locale.US, "%.2f", value);
202    }
203
204    /**
205     * Custom serializer for Double values.
206     * Serializes whole numbers without decimal point.
207     */
208    private static class DoubleSerializer implements JsonSerializer<Double> {
209        @Override
210        public JsonElement serialize(Double src, Type typeOfSrc, JsonSerializationContext context) {
211            if (src == null) {
212                return JsonNull.INSTANCE;
213            }
214            if (src.isNaN() || src.isInfinite()) {
215                return new JsonPrimitive(src.toString());
216            }
217            if (src == src.longValue()) {
218                return new JsonPrimitive(src.longValue());
219            }
220            return new JsonPrimitive(src);
221        }
222    }
223
224    /**
225     * Custom serializer for Instant values.
226     * Serializes to ISO-8601 format.
227     */
228    private static class InstantSerializer implements JsonSerializer<Instant> {
229        @Override
230        public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) {
231            if (src == null) {
232                return JsonNull.INSTANCE;
233            }
234            return new JsonPrimitive(ISO_FORMATTER.format(src));
235        }
236    }
237
238}