Skip to content

Commit 80fe9c1

Browse files
authored
Remove extra spaces in Json and make it same as Items as in V1 (#3835)
* Remove extra spaces in Json and make it same as Items as in V1 * Moved Json string helper functions to seperate class
1 parent 0c074c0 commit 80fe9c1

File tree

5 files changed

+211
-87
lines changed

5 files changed

+211
-87
lines changed

services-custom/dynamodb-enhanced/src/main/java/software/amazon/awssdk/enhanced/dynamodb/internal/document/DefaultEnhancedDocument.java

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
import static java.util.Collections.unmodifiableList;
1919
import static java.util.Collections.unmodifiableMap;
20+
import static software.amazon.awssdk.enhanced.dynamodb.internal.document.JsonStringFormatHelper.addEscapeCharacters;
21+
import static software.amazon.awssdk.enhanced.dynamodb.internal.document.JsonStringFormatHelper.stringValue;
2022

2123
import java.util.ArrayList;
2224
import java.util.Arrays;
@@ -46,7 +48,6 @@
4648
import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser;
4749
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
4850
import software.amazon.awssdk.utils.Lazy;
49-
import software.amazon.awssdk.utils.StringUtils;
5051
import software.amazon.awssdk.utils.Validate;
5152

5253
/**
@@ -181,7 +182,6 @@ public String getJson(String attributeName) {
181182
return null;
182183
}
183184
return JSON_ATTRIBUTE_CONVERTER.transformTo(attributeValue).toString();
184-
// TODO: Does toString return valid JSON? will remove this after comparing V1 side by side.
185185
}
186186

187187
@Override
@@ -215,22 +215,15 @@ public Map<String, AttributeValue> getUnknownTypeMap(String attributeName) {
215215

216216
@Override
217217
public String toJson() {
218-
StringBuilder output = new StringBuilder();
219-
output.append('{');
220-
boolean isFirst = true;
221-
for (Map.Entry<String, AttributeValue> entry : attributeValueMap.getValue().entrySet()) {
222-
if (!isFirst) {
223-
output.append(", ");
224-
} else {
225-
isFirst = false;
226-
}
227-
output.append('"')
228-
.append(StringUtils.replace(entry.getKey(), "\"", "\\"))
229-
.append("\": ")
230-
.append(JSON_ATTRIBUTE_CONVERTER.transformTo(entry.getValue()));
231-
}
232-
output.append('}');
233-
return output.toString();
218+
if (nonAttributeValueMap.isEmpty()) {
219+
return "{}";
220+
}
221+
return attributeValueMap.getValue().entrySet().stream()
222+
.map(entry -> "\""
223+
+ addEscapeCharacters(entry.getKey())
224+
+ "\":"
225+
+ stringValue(JSON_ATTRIBUTE_CONVERTER.transformTo(entry.getValue())))
226+
.collect(Collectors.joining(",", "{", "}"));
234227
}
235228

236229
@Override
@@ -286,6 +279,7 @@ public DefaultBuilder(DefaultEnhancedDocument enhancedDocument) {
286279

287280
public Builder putObject(String attributeName, Object value) {
288281
Validate.paramNotNull(attributeName, "attributeName");
282+
Validate.paramNotBlank(attributeName.trim(), "attributeName");
289283
enhancedTypeMap.remove(attributeName);
290284
nonAttributeValueMap.remove(attributeName);
291285
nonAttributeValueMap.put(attributeName, value);
@@ -435,6 +429,7 @@ private static AttributeValue getAttributeValueFromJson(String json) {
435429

436430
private static void checkInvalidAttribute(String attributeName, Object value) {
437431
Validate.paramNotNull(attributeName, "attributeName");
432+
Validate.paramNotBlank(attributeName.trim(), "attributeName");
438433
Validate.notNull(value, "%s must not be null. Use putNull API to insert a Null value", value);
439434
}
440435
}
@@ -461,4 +456,5 @@ public int hashCode() {
461456
return result;
462457
}
463458

459+
464460
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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.enhanced.dynamodb.internal.document;
17+
18+
import java.util.Map;
19+
import java.util.stream.Collectors;
20+
import java.util.stream.StreamSupport;
21+
import software.amazon.awssdk.annotations.SdkInternalApi;
22+
import software.amazon.awssdk.protocols.jsoncore.JsonNode;
23+
24+
@SdkInternalApi
25+
public final class JsonStringFormatHelper {
26+
27+
private JsonStringFormatHelper() {
28+
}
29+
30+
/**
31+
* Helper function to convert a JsonNode to Json String representation
32+
*
33+
* @param jsonNode The JsonNode that needs to be converted to Json String.
34+
* @return Json String of Json Node.
35+
*/
36+
public static String stringValue(JsonNode jsonNode) {
37+
if (jsonNode.isArray()) {
38+
return StreamSupport.stream(jsonNode.asArray().spliterator(), false)
39+
.map(JsonStringFormatHelper::stringValue)
40+
.collect(Collectors.joining(",", "[", "]"));
41+
}
42+
if (jsonNode.isObject()) {
43+
return mapToString(jsonNode);
44+
}
45+
return jsonNode.isString() ? "\"" + addEscapeCharacters(jsonNode.text()) + "\"" : jsonNode.toString();
46+
}
47+
48+
/**
49+
* Escapes characters for a give given string
50+
*
51+
* @param input Input string
52+
* @return String with escaped characters.
53+
*/
54+
public static String addEscapeCharacters(String input) {
55+
StringBuilder output = new StringBuilder();
56+
for (int i = 0; i < input.length(); i++) {
57+
char ch = input.charAt(i);
58+
switch (ch) {
59+
case '\\':
60+
output.append("\\\\"); // escape backslash with a backslash
61+
break;
62+
case '\n':
63+
output.append("\\n"); // newline character
64+
break;
65+
case '\r':
66+
output.append("\\r"); // carriage return character
67+
break;
68+
case '\t':
69+
output.append("\\t"); // tab character
70+
break;
71+
case '\f':
72+
output.append("\\f"); // form feed
73+
break;
74+
case '\"':
75+
output.append("\\\""); // double-quote character
76+
break;
77+
case '\'':
78+
output.append("\\'"); // single-quote character
79+
break;
80+
default:
81+
output.append(ch);
82+
break;
83+
}
84+
}
85+
return output.toString();
86+
}
87+
88+
private static String mapToString(JsonNode jsonNode) {
89+
Map<String, JsonNode> value = jsonNode.asObject();
90+
91+
if (value.isEmpty()) {
92+
return "{}";
93+
}
94+
95+
StringBuilder output = new StringBuilder();
96+
output.append("{");
97+
value.forEach((k, v) -> output.append("\"").append(k).append("\":")
98+
.append(stringValue(v)).append(","));
99+
output.setCharAt(output.length() - 1, '}');
100+
return output.toString();
101+
}
102+
}

services-custom/dynamodb-enhanced/src/test/java/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocumentTest.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package software.amazon.awssdk.enhanced.dynamodb.document;
1717

1818
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
19+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
1920
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2021
import static org.assertj.core.api.Assertions.assertThatNullPointerException;
2122
import static software.amazon.awssdk.enhanced.dynamodb.AttributeConverterProvider.defaultProvider;
@@ -34,6 +35,10 @@
3435
import java.util.stream.Collectors;
3536
import java.util.stream.Stream;
3637
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.params.ParameterizedTest;
39+
import org.junit.jupiter.params.provider.Arguments;
40+
import org.junit.jupiter.params.provider.MethodSource;
41+
import org.junit.jupiter.params.provider.ValueSource;
3742
import software.amazon.awssdk.core.SdkBytes;
3843
import software.amazon.awssdk.core.SdkNumber;
3944
import software.amazon.awssdk.enhanced.dynamodb.EnhancedType;
@@ -75,6 +80,7 @@ void enhancedDocumentGetters() {
7580
assertThat(document.getMap("simpleMap", EnhancedType.of(UUID.class), EnhancedType.of(BigDecimal.class)))
7681
.containsExactlyEntriesOf(expectedUuidBigDecimalMap);
7782
}
83+
7884
@Test
7985
void testNullArgsInStaticConstructor() {
8086
assertThatNullPointerException()
@@ -114,8 +120,7 @@ void builder_ResetsTheOldValues_beforeJsonSetterIsCalled() {
114120
.putString("simpleKeyNew", "simpleValueNew")
115121
.build();
116122

117-
assertThat(enhancedDocument.toJson()).isEqualTo("{\"stringKey\": \"stringValue\", \"simpleKeyNew\": "
118-
+ "\"simpleValueNew\"}");
123+
assertThat(enhancedDocument.toJson()).isEqualTo("{\"stringKey\":\"stringValue\",\"simpleKeyNew\":\"simpleValueNew\"}");
119124
assertThat(enhancedDocument.getString("simpleKeyOriginal")).isNull();
120125

121126
}
@@ -227,8 +232,8 @@ void error_When_DefaultProviderIsPlacedCustomProvider() {
227232
EnhancedType.of(CustomClassForDocumentAPI.class))
228233
.build();
229234

230-
assertThat(afterCustomClass.toJson()).isEqualTo("{\"direct_attr\": \"sample_value\", \"customObject\": "
231-
+ "{\"longNumber\": 26,\"string\": \"str_one\"}}");
235+
assertThat(afterCustomClass.toJson()).isEqualTo("{\"direct_attr\":\"sample_value\",\"customObject\":{\"longNumber\":26,"
236+
+ "\"string\":\"str_one\"}}");
232237

233238
EnhancedDocument enhancedDocument = EnhancedDocument.builder()
234239
.putString("direct_attr", "sample_value")
@@ -242,4 +247,40 @@ void error_When_DefaultProviderIsPlacedCustomProvider() {
242247
).withMessage("Converter not found for "
243248
+ "EnhancedType(software.amazon.awssdk.enhanced.dynamodb.converters.document.CustomClassForDocumentAPI)");
244249
}
250+
251+
private static Stream<Arguments> escapeDocumentStrings() {
252+
char c = 0x0a;
253+
return Stream.of(
254+
Arguments.of(String.valueOf(c),"{\"key\":\"\\n\"}")
255+
, Arguments.of("","{\"key\":\"\"}")
256+
, Arguments.of(" ","{\"key\":\" \"}")
257+
, Arguments.of("\t","{\"key\":\"\\t\"}")
258+
, Arguments.of("\n","{\"key\":\"\\n\"}")
259+
, Arguments.of("\r","{\"key\":\"\\r\"}")
260+
, Arguments.of("\f", "{\"key\":\"\\f\"}")
261+
);
262+
}
263+
264+
@ParameterizedTest
265+
@ValueSource(strings = {"", " " , "\t", " ", "\n", "\r", "\f"})
266+
void invalidKeyNames(String escapingString){
267+
assertThatIllegalArgumentException().isThrownBy(() ->
268+
EnhancedDocument.builder()
269+
.attributeConverterProviders(defaultProvider())
270+
.putString(escapingString, "sample")
271+
.build())
272+
.withMessageContaining("attributeName must not be blank or empty.");
273+
274+
}
275+
276+
@ParameterizedTest
277+
@MethodSource("escapeDocumentStrings")
278+
void escapingTheValues(String escapingString, String expectedJson) {
279+
280+
EnhancedDocument document = EnhancedDocument.builder()
281+
.attributeConverterProviders(defaultProvider())
282+
.putString("key", escapingString)
283+
.build();
284+
assertThat(document.toJson()).isEqualTo(expectedJson);
285+
}
245286
}

0 commit comments

Comments
 (0)