Skip to content

Commit 0441df6

Browse files
authored
Increase Jackson maxNameLength to support DynamoDB attribute names up to 64KB (#6913)
1 parent 1483d30 commit 0441df6

4 files changed

Lines changed: 151 additions & 0 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS SDK for Java v2",
4+
"contributor": "",
5+
"description": "Fixed deserialization failure for JSON responses containing field names longer than 50,000 characters. Services like DynamoDB allow attribute names up to 65,535 bytes, which exceeded Jackson's default `maxNameLength` limit."
6+
}

core/protocols/aws-json-protocol/src/main/java/software/amazon/awssdk/protocols/json/internal/AwsStructuredPlainJsonFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.awssdk.protocols.json.SdkJsonGenerator;
2121
import software.amazon.awssdk.protocols.json.StructuredJsonGenerator;
2222
import software.amazon.awssdk.thirdparty.jackson.core.JsonFactory;
23+
import software.amazon.awssdk.thirdparty.jackson.core.StreamReadConstraints;
2324
import software.amazon.awssdk.thirdparty.jackson.core.StreamReadFeature;
2425
import software.amazon.awssdk.thirdparty.jackson.core.StreamWriteFeature;
2526

@@ -37,6 +38,13 @@ public final class AwsStructuredPlainJsonFactory {
3738
.enable(StreamReadFeature.USE_FAST_BIG_NUMBER_PARSER)
3839
.enable(StreamReadFeature.USE_FAST_DOUBLE_PARSER)
3940
.enable(StreamWriteFeature.USE_FAST_DOUBLE_WRITER)
41+
// Override Jackson's default maxNameLength of 50,000 to support
42+
// DynamoDB attribute names, which can be up to 65,535 bytes.
43+
// See https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html
44+
.streamReadConstraints(
45+
StreamReadConstraints.builder()
46+
.maxNameLength(65_535)
47+
.build())
4048
.build();
4149

4250
public static final BaseAwsStructuredJsonFactory SDK_JSON_FACTORY = new BaseAwsStructuredJsonFactory(JSON_FACTORY) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.protocols.json.internal;
17+
18+
import static org.assertj.core.api.Assertions.assertThatNoException;
19+
20+
import java.util.Arrays;
21+
import org.junit.jupiter.api.Test;
22+
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
23+
import software.amazon.awssdk.thirdparty.jackson.core.JsonFactory;
24+
25+
class AwsStructuredPlainJsonFactoryTest {
26+
27+
private static final JsonFactory JSON_FACTORY = AwsStructuredPlainJsonFactory.SDK_JSON_FACTORY.getJsonFactory();
28+
29+
@Test
30+
void parse_fieldNameAtMaxDynamoDbLength_succeeds() {
31+
char[] chars = new char[65_535];
32+
Arrays.fill(chars, 'a');
33+
String name = new String(chars);
34+
String json = "{\"" + name + "\": \"value\"}";
35+
36+
assertThatNoException().isThrownBy(() ->
37+
JsonNodeParser.builder().jsonFactory(JSON_FACTORY).build().parse(json));
38+
}
39+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
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+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.dynamodb;
17+
18+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
19+
import static com.github.tomakehurst.wiremock.client.WireMock.any;
20+
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
24+
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
25+
import java.net.URI;
26+
import java.util.Arrays;
27+
import java.util.Collections;
28+
import org.junit.jupiter.api.Test;
29+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
30+
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
31+
import software.amazon.awssdk.regions.Region;
32+
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
33+
import software.amazon.awssdk.services.dynamodb.model.GetItemResponse;
34+
35+
/**
36+
* Regression test for long DynamoDB attribute names. DynamoDB allows attribute names up to 65,535 bytes,
37+
* but Jackson's default maxNameLength of 50,000 caused deserialization failures for names exceeding that limit.
38+
*
39+
* @see <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html">
40+
* DynamoDB Naming Rules</a>
41+
*/
42+
@WireMockTest
43+
class LongAttributeNameTest {
44+
45+
private static final StaticCredentialsProvider CREDENTIALS =
46+
StaticCredentialsProvider.create(AwsBasicCredentials.create("test", "test"));
47+
48+
private static final String LONG_ATTR_NAME;
49+
private static final String RESPONSE_BODY;
50+
51+
static {
52+
char[] chars = new char[65_535];
53+
Arrays.fill(chars, 'a');
54+
LONG_ATTR_NAME = new String(chars);
55+
RESPONSE_BODY = "{\"Item\": {\"pk\": {\"S\": \"test1\"}, \"" + LONG_ATTR_NAME + "\": {\"S\": \"value\"}}}";
56+
}
57+
58+
private void stubResponse(WireMockRuntimeInfo wmInfo) {
59+
wmInfo.getWireMock().register(any(urlEqualTo("/"))
60+
.willReturn(aResponse().withStatus(200).withBody(RESPONSE_BODY)));
61+
}
62+
63+
@Test
64+
void syncClient_getItem_withLongAttributeName_succeeds(WireMockRuntimeInfo wmInfo) {
65+
stubResponse(wmInfo);
66+
67+
DynamoDbClient client = DynamoDbClient.builder()
68+
.endpointOverride(URI.create(wmInfo.getHttpBaseUrl()))
69+
.region(Region.US_EAST_1)
70+
.credentialsProvider(CREDENTIALS)
71+
.build();
72+
73+
GetItemResponse response = client.getItem(r -> r.tableName("test")
74+
.key(Collections.singletonMap("pk",
75+
AttributeValue.fromS("test1"))));
76+
77+
assertThat(response.item()).containsKey(LONG_ATTR_NAME);
78+
assertThat(response.item().get(LONG_ATTR_NAME).s()).isEqualTo("value");
79+
}
80+
81+
@Test
82+
void asyncClient_getItem_withLongAttributeName_succeeds(WireMockRuntimeInfo wmInfo) {
83+
stubResponse(wmInfo);
84+
85+
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder()
86+
.endpointOverride(URI.create(wmInfo.getHttpBaseUrl()))
87+
.region(Region.US_EAST_1)
88+
.credentialsProvider(CREDENTIALS)
89+
.build();
90+
91+
GetItemResponse response = client.getItem(r -> r.tableName("test")
92+
.key(Collections.singletonMap("pk",
93+
AttributeValue.fromS("test1")))).join();
94+
95+
assertThat(response.item()).containsKey(LONG_ATTR_NAME);
96+
assertThat(response.item().get(LONG_ATTR_NAME).s()).isEqualTo("value");
97+
}
98+
}

0 commit comments

Comments
 (0)