-
Notifications
You must be signed in to change notification settings - Fork 28
Releasing v4.11.0 #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4
Are you sure you want to change the base?
Releasing v4.11.0 #107
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| 4.10.0 | ||
| 4.11.0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,19 @@ | ||
| package com.chargebee.v4.internal; | ||
|
|
||
| import com.google.gson.Gson; | ||
| import com.google.gson.GsonBuilder; | ||
| import com.google.gson.JsonArray; | ||
| import com.google.gson.JsonElement; | ||
| import com.google.gson.JsonNull; | ||
| import com.google.gson.JsonObject; | ||
| import com.google.gson.JsonParser; | ||
| import com.google.gson.JsonPrimitive; | ||
| import com.google.gson.JsonSerializer; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.sql.Timestamp; | ||
| import java.text.SimpleDateFormat; | ||
| import java.util.ArrayList; | ||
| import java.util.Date; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
@@ -19,6 +23,29 @@ | |
| /** Gson-backed JSON parsing utility. */ | ||
| public class JsonUtil { | ||
|
|
||
| /** | ||
| * Gson instance configured with Chargebee-specific serializers so that | ||
| * non-trivial leaf types are emitted in the shape the Chargebee API expects: | ||
| * <ul> | ||
| * <li>{@link Timestamp} → Unix seconds (JSON number)</li> | ||
| * <li>{@link Date} → {@code "yyyy-MM-dd"} JSON string</li> | ||
| * <li>{@link Enum} → lowercase {@code name()} JSON string</li> | ||
| * </ul> | ||
| * HTML escaping is disabled so characters like {@code <}, {@code >}, {@code &} | ||
| * pass through verbatim (matches the previous {@code JsonElement.toString()} behavior). | ||
| */ | ||
| private static final Gson GSON = new GsonBuilder() | ||
| .disableHtmlEscaping() | ||
| .serializeNulls() | ||
| .registerTypeAdapter(Timestamp.class, | ||
| (JsonSerializer<Timestamp>) (src, t, c) -> new JsonPrimitive(src.getTime())) | ||
| .registerTypeAdapter(Date.class, | ||
| (JsonSerializer<Date>) (src, t, c) -> | ||
| new JsonPrimitive(new SimpleDateFormat("yyyy-MM-dd").format(src))) | ||
| .registerTypeHierarchyAdapter(Enum.class, | ||
| (JsonSerializer<Enum<?>>) (src, t, c) -> new JsonPrimitive(src.name().toLowerCase())) | ||
|
Comment on lines
+45
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major 🧩 Analysis chain🌐 Web query:
💡 Result: Yes, the parameterless String.toLowerCase method uses the default locale of the Java Virtual Machine (JVM), which is determined by Locale.getDefault [1][2][3]. Because the default locale depends on the environment where the code runs, using the parameterless toLowerCase or toUpperCase methods for strings that are intended to be interpreted in a locale-independent manner—such as protocol keys, programming language identifiers, filenames, or HTML tags—is strongly discouraged [2][3][4]. These methods can produce unexpected results depending on the host system's configuration. A classic example is the Turkish locale, where the character 'I' is converted to a lowercase dotless 'ı' instead of the standard 'i' [2][3][5][4]. For protocol/API values and other machine-facing identifiers, using Locale.ROOT is the recommended practice [2][3][6]. Locale.ROOT is the language- and country-neutral locale, ensuring that case conversion behaves deterministically regardless of the JVM's default locale settings [7][6][8][9]. Using Locale.ROOT (e.g., string.toLowerCase(Locale.ROOT)) provides a stable, predictable outcome suitable for canonicalization, storage, and comparison of technical identifiers [6][10][11]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '--- JsonUtil.java ---\n'
sed -n '1,140p' src/main/java/com/chargebee/v4/internal/JsonUtil.java | nl -ba
printf '\n--- Search toLowerCase/Locale in repo ---\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 253 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '--- JsonUtil.java ---\n'
sed -n '1,140p' src/main/java/com/chargebee/v4/internal/JsonUtil.java | nl -ba
printf '\n--- Search toLowerCase/Locale in repo ---\n'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 253 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- JsonUtil.java ---'
sed -n '1,140p' src/main/java/com/chargebee/v4/internal/JsonUtil.java | nl -ba
echo
echo '--- Search toLowerCase/Locale in repo ---'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 224 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- JsonUtil.java ---'
sed -n '1,140p' src/main/java/com/chargebee/v4/internal/JsonUtil.java | nl -ba
echo
echo '--- Search toLowerCase/Locale in repo ---'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 224 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- JsonUtil.java ---'
awk 'NR>=1 && NR<=140 { printf("%4d %s\n", NR, $0) }' src/main/java/com/chargebee/v4/internal/JsonUtil.java
echo
echo '--- Search toLowerCase/Locale in repo ---'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 8062 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- JsonUtil.java ---'
awk 'NR>=1 && NR<=140 { printf("%4d %s\n", NR, $0) }' src/main/java/com/chargebee/v4/internal/JsonUtil.java
echo
echo '--- Search toLowerCase/Locale in repo ---'
rg -n --hidden --glob '!**/target/**' --glob '!**/build/**' --glob '!**/dist/**' 'toLowerCase\(|Locale\.ROOT|Locale\.getDefault|registerTypeHierarchyAdapter\(Enum\.class' src test .Repository: chargebee/chargebee-java Length of output: 8062 Use 🤖 Prompt for AI Agents |
||
| .create(); | ||
|
|
||
| private JsonUtil() {} | ||
|
|
||
| // --- Parse entry points --- | ||
|
|
@@ -320,25 +347,16 @@ public static Map<String, Object> extractConsentFields(JsonObject obj, Set<Strin | |
|
|
||
| // --- Serialization --- | ||
|
|
||
| /** Serializes a Map to a JSON string. */ | ||
| @SuppressWarnings("unchecked") | ||
| /** Serializes a Map to a JSON string using the configured {@link #GSON} instance. */ | ||
| public static String toJson(Map<String, Object> map) { | ||
| if (map == null || map.isEmpty()) return "{}"; | ||
| JsonObject obj = new JsonObject(); | ||
| for (Map.Entry<String, Object> entry : map.entrySet()) { | ||
| obj.add(entry.getKey(), toJsonElement(entry.getValue())); | ||
| } | ||
| return obj.toString(); | ||
| return GSON.toJson(map); | ||
| } | ||
|
|
||
| /** Serializes a List to a JSON string. */ | ||
| /** Serializes a List to a JSON string using the configured {@link #GSON} instance. */ | ||
| public static String toJson(List<?> list) { | ||
| if (list == null || list.isEmpty()) return "[]"; | ||
| JsonArray array = new JsonArray(); | ||
| for (Object item : list) { | ||
| array.add(toJsonElement(item)); | ||
| } | ||
| return array.toString(); | ||
| return GSON.toJson(list); | ||
| } | ||
|
|
||
| // --- Internal helpers --- | ||
|
|
@@ -365,27 +383,4 @@ private static Object toJavaValue(JsonElement value) { | |
| } | ||
| return null; | ||
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| private static JsonElement toJsonElement(Object value) { | ||
| if (value == null) return JsonNull.INSTANCE; | ||
| if (value instanceof String) return new JsonPrimitive((String) value); | ||
| if (value instanceof Number) return new JsonPrimitive((Number) value); | ||
| if (value instanceof Boolean) return new JsonPrimitive((Boolean) value); | ||
| if (value instanceof Map) { | ||
| JsonObject obj = new JsonObject(); | ||
| for (Map.Entry<String, Object> entry : ((Map<String, Object>) value).entrySet()) { | ||
| obj.add(entry.getKey(), toJsonElement(entry.getValue())); | ||
| } | ||
| return obj; | ||
| } | ||
| if (value instanceof List) { | ||
| JsonArray array = new JsonArray(); | ||
| for (Object item : (List<?>) value) { | ||
| array.add(toJsonElement(item)); | ||
| } | ||
| return array; | ||
| } | ||
| return new JsonPrimitive(value.toString()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Serialize
Timestampin Unix seconds, not milliseconds.Line 41 currently writes
src.getTime(), which is epoch milliseconds. The new contract,getTimestamp(...), and the added regression tests all expect epoch seconds, so JSON requests will still sendexpires_at1000x too large and miss the release’s bug fix.Proposed fix
This contradicts the PR objective and the added regression assertions.
📝 Committable suggestion
🤖 Prompt for AI Agents