Skip to content

Commit eadc816

Browse files
committed
Add support for custom GSON type adapter factories
It is not currently possible to add custom Gson type adapters in a modular way at runtime. Adding them requires manually instantiating GsonSupport, which undermines the modular approach of extending Helidon with media type implementations. To resolve this, I have added the ability to add custom TypeAdapterFactory instances as service providers. These providers are loaded during the initialization of GsonSupport by the GsonMediaSupportProvider using Java's ServiceLoader API. This allows for a more flexible and modular way to extend Gson's functionality within Helidon.
1 parent 2d4bf6b commit eadc816

File tree

4 files changed

+134
-0
lines changed

4 files changed

+134
-0
lines changed

http/media/gson/src/main/java/io/helidon/http/media/gson/GsonSupport.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.helidon.http.media.gson;
1717

1818
import java.util.Objects;
19+
import java.util.ServiceLoader;
1920

2021
import io.helidon.common.GenericType;
2122
import io.helidon.common.config.Config;
@@ -30,6 +31,7 @@
3031

3132
import com.google.gson.Gson;
3233
import com.google.gson.GsonBuilder;
34+
import com.google.gson.TypeAdapterFactory;
3335

3436
import static io.helidon.http.HeaderValues.CONTENT_TYPE_JSON;
3537

@@ -73,6 +75,10 @@ public static MediaSupport create(Config config, String name) {
7375
Objects.requireNonNull(name, "Name must not be null");
7476

7577
GsonBuilder gsonBuilder = new GsonBuilder();
78+
// Enable the registering of custom type adapters by using service providers for TypeAdapterFactory.
79+
for (var factory : ServiceLoader.load(TypeAdapterFactory.class)) {
80+
gsonBuilder.registerTypeAdapterFactory(factory);
81+
}
7682
Gson gson = gsonBuilder.create();
7783
return create(gson, name);
7884
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.http.media.gson;
18+
19+
import io.helidon.common.GenericType;
20+
import io.helidon.common.config.Config;
21+
import io.helidon.http.WritableHeaders;
22+
import org.junit.jupiter.api.Test;
23+
24+
import java.io.ByteArrayInputStream;
25+
import java.io.ByteArrayOutputStream;
26+
27+
import static org.hamcrest.CoreMatchers.is;
28+
import static org.hamcrest.MatcherAssert.assertThat;
29+
30+
class GsonSupportTest {
31+
32+
record Book(String title, int pages) {
33+
}
34+
35+
@Test
36+
void test() {
37+
var support = GsonSupport.create(Config.empty(), "gson");
38+
var headers = WritableHeaders.create();
39+
var type = GenericType.create(Book.class);
40+
var outputStream = new ByteArrayOutputStream();
41+
var instance = new Book("some-title", 123);
42+
43+
support.writer(type, headers)
44+
.supplier()
45+
.get()
46+
.write(type, instance, outputStream, headers);
47+
48+
assertThat(GsonSupportTestBookTypeAdapterFactory.writeCount.get(), is(1));
49+
50+
Book sanity = support.reader(type, headers)
51+
.supplier()
52+
.get()
53+
.read(type, new ByteArrayInputStream(outputStream.toByteArray()), headers);
54+
55+
assertThat(GsonSupportTestBookTypeAdapterFactory.readCount.get(), is(1));
56+
57+
assertThat(sanity.title(), is("some-title"));
58+
assertThat(sanity.pages(), is(123));
59+
assertThat(sanity, is(instance));
60+
}
61+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2025 Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.helidon.http.media.gson;
18+
19+
import com.google.gson.Gson;
20+
import com.google.gson.TypeAdapter;
21+
import com.google.gson.TypeAdapterFactory;
22+
import com.google.gson.reflect.TypeToken;
23+
import com.google.gson.stream.JsonReader;
24+
import com.google.gson.stream.JsonWriter;
25+
26+
import java.io.IOException;
27+
import java.util.concurrent.atomic.AtomicInteger;
28+
29+
public class GsonSupportTestBookTypeAdapterFactory implements TypeAdapterFactory {
30+
31+
static final AtomicInteger readCount = new AtomicInteger(0);
32+
static final AtomicInteger writeCount = new AtomicInteger(0);
33+
34+
private static final TypeAdapter instance = new TypeAdapter<GsonSupportTest.Book>() {
35+
@Override
36+
public void write(JsonWriter writer, GsonSupportTest.Book book) throws IOException {
37+
writer.beginObject();
38+
writer.name("title");
39+
writer.value(book.title());
40+
writer.name("pages");
41+
writer.value(book.pages());
42+
writer.endObject();
43+
writeCount.incrementAndGet();
44+
}
45+
46+
@Override
47+
public GsonSupportTest.Book read(JsonReader reader) throws IOException {
48+
reader.beginObject();
49+
reader.nextName();
50+
var title = reader.nextString();
51+
reader.nextName();
52+
var pages = reader.nextInt();
53+
reader.endObject();
54+
readCount.incrementAndGet();
55+
return new GsonSupportTest.Book(title, pages);
56+
}
57+
};
58+
59+
@Override
60+
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> typeToken) {
61+
if (typeToken.getRawType().isAssignableFrom(GsonSupportTest.Book.class)) {
62+
return instance;
63+
}
64+
return null;
65+
}
66+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.helidon.http.media.gson.GsonSupportTestBookTypeAdapterFactory

0 commit comments

Comments
 (0)