From be96b4c145dd91f67c16b5f7017f4b64293bbac7 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Fri, 26 Jun 2026 09:39:27 +0300 Subject: [PATCH 1/2] Read an empty Locale string back as Locale.ROOT Gson serializes Locale.ROOT to an empty string, but reading it back threw a NullPointerException: with no tokens the language stayed null and was passed to new Locale(null). Default the language to the empty string so the empty form round-trips to Locale.ROOT. --- .../java/com/google/gson/internal/bind/TypeAdapters.java | 2 +- .../google/gson/functional/DefaultTypeAdaptersTest.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 6e0f85ad6d..084b0fe6df 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -909,7 +909,7 @@ public Locale read(JsonReader in) throws IOException { } String locale = in.nextString(); StringTokenizer tokenizer = new StringTokenizer(locale, "_"); - String language = null; + String language = ""; String country = null; String variant = null; if (tokenizer.hasMoreElements()) { diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index 187c3f2808..0682c22a03 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -263,6 +263,14 @@ public void testLocaleDeserializationWithLanguage() { assertThat(locale.getLanguage()).isEqualTo("en"); } + @Test + public void testLocaleDeserializationRoot() { + // Gson serializes Locale.ROOT to "", so it must read "" back to Locale.ROOT. + String json = "\"\""; + Locale locale = gson.fromJson(json, Locale.class); + assertThat(locale).isEqualTo(Locale.ROOT); + } + @Test public void testLocaleSerializationWithLanguageCountry() { Locale target = Locale.CANADA_FRENCH; From f9afb964cffb5b6245780dd5cff64e70786ffbd5 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Fri, 26 Jun 2026 23:09:28 +0300 Subject: [PATCH 2/2] Use an explicit Locale.ROOT guard and add a serialization test Per review: default language to null and return Locale.ROOT explicitly for an empty tag, drop the redundant variant == null check (country == null already implies it), and add a test that Locale.ROOT serializes to an empty string. --- .../java/com/google/gson/internal/bind/TypeAdapters.java | 7 +++++-- .../google/gson/functional/DefaultTypeAdaptersTest.java | 6 ++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java index 084b0fe6df..dc347e9dc4 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java +++ b/gson/src/main/java/com/google/gson/internal/bind/TypeAdapters.java @@ -909,7 +909,7 @@ public Locale read(JsonReader in) throws IOException { } String locale = in.nextString(); StringTokenizer tokenizer = new StringTokenizer(locale, "_"); - String language = ""; + String language = null; String country = null; String variant = null; if (tokenizer.hasMoreElements()) { @@ -921,7 +921,10 @@ public Locale read(JsonReader in) throws IOException { if (tokenizer.hasMoreElements()) { variant = tokenizer.nextToken(); } - if (country == null && variant == null) { + if (language == null) { + return Locale.ROOT; + } + if (country == null) { return new Locale(language); } else if (variant == null) { return new Locale(language, country); diff --git a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java index 0682c22a03..d80d390bfb 100644 --- a/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java +++ b/gson/src/test/java/com/google/gson/functional/DefaultTypeAdaptersTest.java @@ -271,6 +271,12 @@ public void testLocaleDeserializationRoot() { assertThat(locale).isEqualTo(Locale.ROOT); } + @Test + public void testLocaleSerializationRoot() { + // Locale.ROOT round-trips: it is serialized as an empty string. + assertThat(gson.toJson(Locale.ROOT)).isEqualTo("\"\""); + } + @Test public void testLocaleSerializationWithLanguageCountry() { Locale target = Locale.CANADA_FRENCH;