Skip to content

Commit 9f01de9

Browse files
committed
preserve LazyJsonString interface
1 parent 92b6cbb commit 9f01de9

File tree

4 files changed

+93
-38
lines changed

4 files changed

+93
-38
lines changed
Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,44 @@
11
import { describe, expect, test as it } from "vitest";
22

33
import { LazyJsonString } from "./lazy-json";
4+
45
describe("LazyJsonString", () => {
5-
it("returns identical values for toString(), valueOf(), and toJSON()", () => {
6-
const jsonValue = LazyJsonString.from({ foo: "bar" });
7-
expect(jsonValue.valueOf()).toBe(JSON.stringify({ foo: "bar" }));
8-
expect(jsonValue.toString()).toBe(JSON.stringify({ foo: "bar" }));
9-
expect(jsonValue.toJSON()).toBe(JSON.stringify({ foo: "bar" }));
6+
it("should have string methods", () => {
7+
const jsonValue = new LazyJsonString('"foo"');
8+
expect(jsonValue.length).toBe(5);
9+
expect(jsonValue.toString()).toBe('"foo"');
10+
});
11+
12+
it("should deserialize json properly", () => {
13+
const jsonValue = new LazyJsonString('"foo"');
14+
expect(jsonValue.deserializeJSON()).toBe("foo");
15+
const wrongJsonValue = new LazyJsonString("foo");
16+
expect(() => wrongJsonValue.deserializeJSON()).toThrow();
17+
});
18+
19+
it("should get JSON string properly", () => {
20+
const jsonValue = new LazyJsonString('{"foo", "bar"}');
21+
expect(jsonValue.toJSON()).toBe('{"foo", "bar"}');
1022
});
1123

1224
it("can instantiate from LazyJsonString class", () => {
13-
const original = LazyJsonString.from('"foo"');
25+
const original = new LazyJsonString('"foo"');
1426
const newOne = LazyJsonString.from(original);
1527
expect(newOne.toString()).toBe('"foo"');
1628
});
1729

1830
it("can instantiate from String class", () => {
19-
const jsonValue = LazyJsonString.from('"foo"');
31+
const jsonValue = LazyJsonString.from(new String('"foo"'));
2032
expect(jsonValue.toString()).toBe('"foo"');
2133
});
2234

2335
it("can instantiate from object", () => {
2436
const jsonValue = LazyJsonString.from({ foo: "bar" });
2537
expect(jsonValue.toString()).toBe('{"foo":"bar"}');
2638
});
39+
40+
it("passes instanceof String check", () => {
41+
const jsonValue = LazyJsonString.from({ foo: "bar" });
42+
expect(jsonValue).toBeInstanceOf(String);
43+
});
2744
});
Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,73 @@
1+
/**
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.
12+
*/
13+
export type AutomaticJsonStringConversion = Parameters<typeof JSON.stringify>[0] | LazyJsonString;
14+
115
/**
216
* @internal
317
*
4-
* This class allows the usage of data objects in fields that expect
5-
* JSON strings. It serializes the data object into JSON
6-
* if needed during the request serialization step.
18+
*/
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+
}
32+
33+
/**
34+
* @internal
735
*
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.
842
*/
9-
export class LazyJsonString {
10-
private constructor(private value: string) {}
43+
export function LazyJsonString(val: string): void {
44+
const str = Object.assign(new String(val), {
45+
deserializeJSON() {
46+
return JSON.parse(String(val));
47+
},
1148

12-
public toString(): string {
13-
return this.value;
14-
}
49+
toString() {
50+
return String(val);
51+
},
1552

16-
public valueOf(): string {
17-
return this.value;
18-
}
53+
toJSON() {
54+
return String(val);
55+
},
56+
});
1957

20-
public toJSON(): string {
21-
return this.value;
22-
}
58+
return str as never;
59+
}
2360

24-
public static from(object: any): LazyJsonString {
25-
if (object instanceof LazyJsonString) {
26-
return object;
27-
} else if (typeof object === "string") {
28-
return new LazyJsonString(object);
29-
}
30-
return new LazyJsonString(JSON.stringify(object));
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;
3166
}
67+
return LazyJsonString(JSON.stringify(object)) as any;
68+
};
3269

33-
/**
34-
* @deprecated call from() instead.
35-
*/
36-
public static fromObject(object: any): LazyJsonString {
37-
return LazyJsonString.from(object);
38-
}
39-
}
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/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

0 commit comments

Comments
 (0)