Skip to content

Commit 70275bd

Browse files
authored
remove String extension in LazyJsonString (#1468)
* remove String extension in LazyJsonString * uniform usage of LazyJsonString * preserve LazyJsonString interface
1 parent 7c8345f commit 70275bd

File tree

8 files changed

+86
-55
lines changed

8 files changed

+86
-55
lines changed

.changeset/warm-dragons-wash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/smithy-client": minor
3+
---
4+
5+
remove String extension in LazyJsonString

packages/smithy-client/src/lazy-json.spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { describe, expect, test as it } from "vitest";
22

33
import { LazyJsonString } from "./lazy-json";
4+
45
describe("LazyJsonString", () => {
5-
it("should has string methods", () => {
6+
it("should have string methods", () => {
67
const jsonValue = new LazyJsonString('"foo"');
78
expect(jsonValue.length).toBe(5);
89
expect(jsonValue.toString()).toBe('"foo"');
@@ -22,17 +23,22 @@ describe("LazyJsonString", () => {
2223

2324
it("can instantiate from LazyJsonString class", () => {
2425
const original = new LazyJsonString('"foo"');
25-
const newOne = LazyJsonString.fromObject(original);
26+
const newOne = LazyJsonString.from(original);
2627
expect(newOne.toString()).toBe('"foo"');
2728
});
2829

2930
it("can instantiate from String class", () => {
30-
const jsonValue = LazyJsonString.fromObject(new String('"foo"'));
31+
const jsonValue = LazyJsonString.from(new String('"foo"'));
3132
expect(jsonValue.toString()).toBe('"foo"');
3233
});
3334

3435
it("can instantiate from object", () => {
35-
const jsonValue = LazyJsonString.fromObject({ foo: "bar" });
36+
const jsonValue = LazyJsonString.from({ foo: "bar" });
3637
expect(jsonValue.toString()).toBe('{"foo":"bar"}');
3738
});
39+
40+
it("passes instanceof String check", () => {
41+
const jsonValue = LazyJsonString.from({ foo: "bar" });
42+
expect(jsonValue).toBeInstanceOf(String);
43+
});
3844
});
Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,73 @@
11
/**
2-
* Lazy String holder for JSON typed contents.
2+
* @public
3+
*
4+
* A model field with this type means that you may provide a JavaScript
5+
* object in lieu of a JSON string, and it will be serialized to JSON
6+
* automatically before being sent in a request.
7+
*
8+
* For responses, you will receive a "LazyJsonString", which is a boxed String object
9+
* with additional mixin methods.
10+
* To get the string value, call `.toString()`, or to get the JSON object value,
11+
* call `.deserializeJSON()` or parse it yourself.
312
*/
4-
5-
interface StringWrapper {
6-
new (arg: any): String;
7-
}
13+
export type AutomaticJsonStringConversion = Parameters<typeof JSON.stringify>[0] | LazyJsonString;
814

915
/**
10-
* Because of https://github.com/microsoft/tslib/issues/95,
11-
* TS 'extends' shim doesn't support extending native types like String.
12-
* So here we create StringWrapper that duplicate everything from String
13-
* class including its prototype chain. So we can extend from here.
14-
*
1516
* @internal
17+
*
1618
*/
17-
// @ts-ignore StringWrapper implementation is not a simple constructor
18-
export const StringWrapper: StringWrapper = function () {
19-
//@ts-ignore 'this' cannot be assigned to any, but Object.getPrototypeOf accepts any
20-
const Class = Object.getPrototypeOf(this).constructor;
21-
const Constructor = Function.bind.apply(String, [null as any, ...arguments]);
22-
//@ts-ignore Call wrapped String constructor directly, don't bother typing it.
23-
const instance = new Constructor();
24-
Object.setPrototypeOf(instance, Class.prototype);
25-
return instance as String;
26-
};
27-
StringWrapper.prototype = Object.create(String.prototype, {
28-
constructor: {
29-
value: StringWrapper,
30-
enumerable: false,
31-
writable: true,
32-
configurable: true,
33-
},
34-
});
35-
Object.setPrototypeOf(StringWrapper, String);
19+
export interface LazyJsonString extends String {
20+
new (s: string): typeof LazyJsonString;
21+
22+
/**
23+
* @returns the JSON parsing of the string value.
24+
*/
25+
deserializeJSON(): any;
26+
27+
/**
28+
* @returns the original string value rather than a JSON.stringified value.
29+
*/
30+
toJSON(): string;
31+
}
3632

3733
/**
3834
* @internal
35+
*
36+
* Extension of the native String class in the previous implementation
37+
* has negative global performance impact on method dispatch for strings,
38+
* and is generally discouraged.
39+
*
40+
* This current implementation may look strange, but is necessary to preserve the interface and
41+
* behavior of extending the String class.
3942
*/
40-
export class LazyJsonString extends StringWrapper {
41-
deserializeJSON(): any {
42-
return JSON.parse(super.toString());
43-
}
43+
export function LazyJsonString(val: string): void {
44+
const str = Object.assign(new String(val), {
45+
deserializeJSON() {
46+
return JSON.parse(String(val));
47+
},
4448

45-
toJSON(): string {
46-
return super.toString();
47-
}
49+
toString() {
50+
return String(val);
51+
},
4852

49-
static fromObject(object: any): LazyJsonString {
50-
if (object instanceof LazyJsonString) {
51-
return object;
52-
} else if (object instanceof String || typeof object === "string") {
53-
return new LazyJsonString(object);
54-
}
55-
return new LazyJsonString(JSON.stringify(object));
56-
}
53+
toJSON() {
54+
return String(val);
55+
},
56+
});
57+
58+
return str as never;
5759
}
60+
61+
LazyJsonString.from = (object: any): LazyJsonString => {
62+
if (object && typeof object === "object" && (object instanceof LazyJsonString || "deserializeJSON" in object)) {
63+
return object as any;
64+
} else if (typeof object === "string" || Object.getPrototypeOf(object) === String.prototype) {
65+
return LazyJsonString(String(object) as string) as any;
66+
}
67+
return LazyJsonString(JSON.stringify(object)) as any;
68+
};
69+
70+
/**
71+
* @deprecated use from.
72+
*/
73+
LazyJsonString.fromObject = LazyJsonString.from;

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/SymbolVisitor.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,12 @@ public Symbol stringShape(StringShape shape) {
300300
if (mediaTypeTrait.isPresent()) {
301301
String mediaType = mediaTypeTrait.get().getValue();
302302
if (CodegenUtils.isJsonMediaType(mediaType)) {
303-
Symbol.Builder builder = createSymbolBuilder(shape, "__LazyJsonString | string");
304-
return addSmithyUseImport(builder, "LazyJsonString", "__LazyJsonString").build();
303+
Symbol.Builder builder = createSymbolBuilder(shape, "__AutomaticJsonStringConversion | string");
304+
return addSmithyUseImport(
305+
builder,
306+
"AutomaticJsonStringConversion",
307+
"__AutomaticJsonStringConversion"
308+
).build();
305309
} else {
306310
LOGGER.warning(() -> "Found unsupported mediatype " + mediaType + " on String shape: " + shape);
307311
}

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/HttpProtocolGeneratorUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public static String getStringInputParam(GenerationContext context, Shape shape,
176176
if (CodegenUtils.isJsonMediaType(mediaType)) {
177177
TypeScriptWriter writer = context.getWriter();
178178
writer.addImport("LazyJsonString", "__LazyJsonString", TypeScriptDependency.AWS_SMITHY_CLIENT);
179-
return "__LazyJsonString.fromObject(" + dataSource + ")";
179+
return "__LazyJsonString.from(" + dataSource + ")";
180180
} else {
181181
LOGGER.warning(() -> "Found unsupported mediatype " + mediaType + " on String shape: " + shape);
182182
}
@@ -210,7 +210,7 @@ public static String getStringOutputParam(GenerationContext context,
210210
if (CodegenUtils.isJsonMediaType(mediaType)) {
211211
TypeScriptWriter writer = context.getWriter();
212212
writer.addImport("LazyJsonString", "__LazyJsonString", TypeScriptDependency.AWS_SMITHY_CLIENT);
213-
return "new __LazyJsonString(" + dataSource + ")";
213+
return "__LazyJsonString.from(" + dataSource + ")";
214214
} else {
215215
LOGGER.warning(() -> "Found unsupported mediatype " + mediaType + " on String shape: " + shape);
216216
}

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/SymbolProviderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ public void usesLazyJsonStringForJsonMediaType() {
191191
SymbolProvider provider = new SymbolVisitor(model, settings);
192192
Symbol memberSymbol = provider.toSymbol(member);
193193

194-
assertThat(memberSymbol.getName(), equalTo("__LazyJsonString | string"));
194+
assertThat(memberSymbol.getName(), equalTo("__AutomaticJsonStringConversion | string"));
195195
}
196196

197197
@Test

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/DocumentMemberDeserVisitorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static Collection<Object[]> validMemberTargetTypes() {
9898
{StringShape.builder().id(id).build(), "__expectString(" + DATA_SOURCE + ")", source},
9999
{
100100
StringShape.builder().id(id).addTrait(new MediaTypeTrait("foo+json")).build(),
101-
"new __LazyJsonString(" + DATA_SOURCE + ")",
101+
"__LazyJsonString.from(" + DATA_SOURCE + ")",
102102
source
103103
},
104104
{BlobShape.builder().id(id).build(), "context.base64Decoder(" + DATA_SOURCE + ")", source},

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/DocumentMemberSerVisitorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static Collection<Object[]> validMemberTargetTypes() {
8383
{StringShape.builder().id(id).build(), DATA_SOURCE},
8484
{
8585
StringShape.builder().id(id).addTrait(new MediaTypeTrait("foo+json")).build(),
86-
"__LazyJsonString.fromObject(" + DATA_SOURCE + ")"
86+
"__LazyJsonString.from(" + DATA_SOURCE + ")"
8787
},
8888
{BlobShape.builder().id(id).build(), "context.base64Encoder(" + DATA_SOURCE + ")"},
8989
{DocumentShape.builder().id(id).build(), delegate},

0 commit comments

Comments
 (0)