From 9bea9ac099e4e5fbe4ef5e953c970b0376270f6e Mon Sep 17 00:00:00 2001 From: Bogdan Kyryliuk <5727938+bkyryliuk@users.noreply.github.com> Date: Thu, 8 Oct 2026 09:29:24 +0000 Subject: [PATCH] fix(model): register ListRecord.Field adapter in GsonFactory #1590 added GsonListRecordFieldFactory to handle array-shaped list_record field messages, but only under slack-api-model tests. The production GsonFactory never registered it, so conversations.replies still fails with Expected BEGIN_OBJECT but was BEGIN_ARRAY on Slack List unfurls (#1587, #1653, reproduced in #1644). Move the factory to slack-api-model main next to the other model adapters and register it in GsonFactory.registerTypeAdapters. --- .../com/slack/api/util/json/GsonFactory.java | 4 +- .../json/GsonListRecordFieldFactoryTest.java | 72 +++++++++++++++++++ .../json}/GsonListRecordFieldFactory.java | 0 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 slack-api-client/src/test/java/test_locally/api/util/json/GsonListRecordFieldFactoryTest.java rename slack-api-model/src/{test/java/test_locally/util/list => main/java/com/slack/api/util/json}/GsonListRecordFieldFactory.java (100%) diff --git a/slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java b/slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java index a823465cc..b5c4bc73a 100644 --- a/slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java +++ b/slack-api-client/src/main/java/com/slack/api/util/json/GsonFactory.java @@ -16,6 +16,7 @@ import com.slack.api.model.block.element.RichTextElement; import com.slack.api.model.event.FunctionExecutedEvent; import com.slack.api.model.event.MessageChangedEvent; +import com.slack.api.model.list.ListRecord; import com.slack.api.model.list.ListView; import java.time.Instant; @@ -87,6 +88,7 @@ public static void registerTypeAdapters(GsonBuilder builder, boolean failOnUnkno .registerTypeAdapter(AppWorkflow.StepInputValueElementDefault.class, new GsonAppWorkflowStepInputValueDefaultFactory(failOnUnknownProps)) .registerTypeAdapter(LogsResponse.DetailsChangedValue.class, new GsonAuditLogsDetailsChangedValueFactory(failOnUnknownProps)) .registerTypeAdapter(LogsResponse.UserIDs.class, new GsonAuditLogsDetailsUserIDsFactory(failOnUnknownProps)) - .registerTypeAdapter(ListView.Grouping.class, new GsonListViewGroupingFactory(failOnUnknownProps)); + .registerTypeAdapter(ListView.Grouping.class, new GsonListViewGroupingFactory(failOnUnknownProps)) + .registerTypeAdapter(ListRecord.Field.class, new GsonListRecordFieldFactory(failOnUnknownProps)); } } diff --git a/slack-api-client/src/test/java/test_locally/api/util/json/GsonListRecordFieldFactoryTest.java b/slack-api-client/src/test/java/test_locally/api/util/json/GsonListRecordFieldFactoryTest.java new file mode 100644 index 000000000..869adc591 --- /dev/null +++ b/slack-api-client/src/test/java/test_locally/api/util/json/GsonListRecordFieldFactoryTest.java @@ -0,0 +1,72 @@ +package test_locally.api.util.json; + +import com.google.gson.Gson; +import com.slack.api.methods.response.conversations.ConversationsRepliesResponse; +import com.slack.api.model.list.ListRecord; +import com.slack.api.util.json.GsonFactory; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class GsonListRecordFieldFactoryTest { + + private final Gson gson = GsonFactory.createSnakeCase(); + + private static String repliesWithListRecordFieldMessage(String messageJson) { + return "{\n" + + " \"ok\": true,\n" + + " \"messages\": [\n" + + " {\n" + + " \"type\": \"message\",\n" + + " \"subtype\": \"bot_message\",\n" + + " \"ts\": \"1700000001.000200\",\n" + + " \"text\": \"Request has been marked Completed.\",\n" + + " \"attachments\": [\n" + + " {\n" + + " \"id\": 1,\n" + + " \"list_record\": {\n" + + " \"record\": {\n" + + " \"fields\": [\n" + + " {\n" + + " \"key\": \"Col1\",\n" + + " \"column_id\": \"Col1\",\n" + + " \"value\": \"https://example.com\",\n" + + " \"message\": " + messageJson + "\n" + + " }\n" + + " ]\n" + + " }\n" + + " }\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}"; + } + + private ListRecord.Field firstField(String json) { + ConversationsRepliesResponse response = gson.fromJson(json, ConversationsRepliesResponse.class); + return response.getMessages().get(0).getAttachments().get(0) + .getListRecord().getRecord().getFields().get(0); + } + + @Test + public void conversationsReplies_messageAsArray() { + ListRecord.Field field = firstField(repliesWithListRecordFieldMessage( + "[{\"value\": \"https://example.com\", \"channel_id\": \"C111\", \"ts\": \"1700000000.000100\"}," + + " {\"value\": \"https://example.com/2\", \"channel_id\": \"C111\", \"ts\": \"1700000000.000300\"}]")); + + assertThat(field.getMessages().size(), is(2)); + assertThat(field.getMessages().get(1).getTs(), is("1700000000.000300")); + assertThat(field.getMessage().getTs(), is("1700000000.000100")); + } + + @Test + public void conversationsReplies_messageAsObject() { + ListRecord.Field field = firstField(repliesWithListRecordFieldMessage( + "{\"text\": \"hello\", \"ts\": \"1700000000.000100\"}")); + + assertThat(field.getMessage().getText(), is("hello")); + assertThat(field.getMessages().size(), is(1)); + } +} diff --git a/slack-api-model/src/test/java/test_locally/util/list/GsonListRecordFieldFactory.java b/slack-api-model/src/main/java/com/slack/api/util/json/GsonListRecordFieldFactory.java similarity index 100% rename from slack-api-model/src/test/java/test_locally/util/list/GsonListRecordFieldFactory.java rename to slack-api-model/src/main/java/com/slack/api/util/json/GsonListRecordFieldFactory.java