Skip to content

Commit 0fee6d0

Browse files
committed
refactor: Extract shared JsonStrings helper
1 parent a63321f commit 0fee6d0

3 files changed

Lines changed: 63 additions & 80 deletions

File tree

src/main/java/com/retailsvc/http/internal/HealthRenderer.java

Lines changed: 3 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,12 @@ public final class HealthRenderer {
1515
/** Initial capacity sized for a typical health document with a handful of dependencies. */
1616
private static final int INITIAL_BUFFER_CAPACITY = 128;
1717

18-
/** Codepoints below this value are control characters and must be unicode-escaped in JSON. */
19-
private static final int FIRST_PRINTABLE_ASCII = 0x20;
20-
2118
private HealthRenderer() {}
2219

2320
public static String toJson(HealthOutcome outcome) {
2421
StringBuilder out = new StringBuilder(INITIAL_BUFFER_CAPACITY);
2522
out.append('{');
26-
appendStringField(out, "outcome", outcome.outcome());
23+
JsonStrings.appendStringField(out, "outcome", outcome.outcome());
2724
out.append(",\"dependencies\":[");
2825
appendDependencies(out, outcome.dependencies());
2926
out.append("]}");
@@ -37,40 +34,10 @@ private static void appendDependencies(StringBuilder out, List<Dependency> deps)
3734
}
3835
Dependency d = deps.get(i);
3936
out.append('{');
40-
appendStringField(out, "id", d.id());
37+
JsonStrings.appendStringField(out, "id", d.id());
4138
out.append(',');
42-
appendStringField(out, "status", d.status());
39+
JsonStrings.appendStringField(out, "status", d.status());
4340
out.append('}');
4441
}
4542
}
46-
47-
private static void appendStringField(StringBuilder out, String name, String value) {
48-
out.append('"').append(name).append("\":\"");
49-
appendEscaped(out, value);
50-
out.append('"');
51-
}
52-
53-
private static void appendEscaped(StringBuilder out, String value) {
54-
for (int i = 0; i < value.length(); i++) {
55-
char c = value.charAt(i);
56-
switch (c) {
57-
case '\\' -> out.append("\\\\");
58-
case '"' -> out.append("\\\"");
59-
case '\n' -> out.append("\\n");
60-
case '\r' -> out.append("\\r");
61-
case '\t' -> out.append("\\t");
62-
case '\b' -> out.append("\\b");
63-
case '\f' -> out.append("\\f");
64-
default -> appendUnicodeOrLiteral(out, c);
65-
}
66-
}
67-
}
68-
69-
private static void appendUnicodeOrLiteral(StringBuilder out, char c) {
70-
if (c < FIRST_PRINTABLE_ASCII) {
71-
out.append(String.format("\\u%04x", (int) c));
72-
} else {
73-
out.append(c);
74-
}
75-
}
7643
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.retailsvc.http.internal;
2+
3+
/**
4+
* Small JSON string-writing helpers shared by the library's hand-rolled renderers.
5+
*
6+
* <p>Used in place of a full JSON writer because the library only ever emits a handful of fixed
7+
* fields with known shapes. Package-private — not part of the public API.
8+
*/
9+
final class JsonStrings {
10+
11+
/** Codepoints below this value are control characters and must be unicode-escaped in JSON. */
12+
private static final int FIRST_PRINTABLE_ASCII = 0x20;
13+
14+
private JsonStrings() {}
15+
16+
/** Appends {@code "name":"<escaped value>"} to {@code out}. */
17+
static void appendStringField(StringBuilder out, String name, String value) {
18+
out.append('"').append(name).append("\":\"");
19+
appendEscaped(out, value);
20+
out.append('"');
21+
}
22+
23+
/**
24+
* Appends {@code value} with JSON-string escaping applied. Handles the seven mandatory escape
25+
* sequences ({@code \\}, {@code \"}, {@code \n}, {@code \r}, {@code \t}, {@code \b}, {@code \f})
26+
* and emits a {@code &#92;uXXXX} sequence for any remaining control character below {@link
27+
* #FIRST_PRINTABLE_ASCII}.
28+
*/
29+
static void appendEscaped(StringBuilder out, String value) {
30+
for (int i = 0; i < value.length(); i++) {
31+
char c = value.charAt(i);
32+
switch (c) {
33+
case '\\' -> out.append("\\\\");
34+
case '"' -> out.append("\\\"");
35+
case '\n' -> out.append("\\n");
36+
case '\r' -> out.append("\\r");
37+
case '\t' -> out.append("\\t");
38+
case '\b' -> out.append("\\b");
39+
case '\f' -> out.append("\\f");
40+
default -> appendUnicodeOrLiteral(out, c);
41+
}
42+
}
43+
}
44+
45+
private static void appendUnicodeOrLiteral(StringBuilder out, char c) {
46+
if (c < FIRST_PRINTABLE_ASCII) {
47+
out.append(String.format("\\u%04x", (int) c));
48+
} else {
49+
out.append(c);
50+
}
51+
}
52+
}

src/main/java/com/retailsvc/http/internal/ProblemDetailRenderer.java

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,77 +17,41 @@ public final class ProblemDetailRenderer {
1717
/** Initial capacity of the JSON buffer; sized for a typical problem-detail document. */
1818
private static final int INITIAL_BUFFER_CAPACITY = 128;
1919

20-
/** Codepoints below this value are control characters and must be unicode-escaped in JSON. */
21-
private static final int FIRST_PRINTABLE_ASCII = 0x20;
22-
2320
private ProblemDetailRenderer() {}
2421

2522
public static String render(int status, String title, String detail) {
2623
StringBuilder out = new StringBuilder(INITIAL_BUFFER_CAPACITY);
2724
out.append('{');
28-
appendStringField(out, "type", PROBLEM_TYPE);
25+
JsonStrings.appendStringField(out, "type", PROBLEM_TYPE);
2926
out.append(',');
30-
appendStringField(out, "title", title);
27+
JsonStrings.appendStringField(out, "title", title);
3128
out.append(',');
3229
appendIntField(out, "status", status);
3330
out.append(',');
34-
appendStringField(out, "detail", detail);
31+
JsonStrings.appendStringField(out, "detail", detail);
3532
out.append('}');
3633
return out.toString();
3734
}
3835

3936
public static String render(ValidationError error) {
4037
StringBuilder out = new StringBuilder(INITIAL_BUFFER_CAPACITY);
4138
out.append('{');
42-
appendStringField(out, "type", PROBLEM_TYPE);
39+
JsonStrings.appendStringField(out, "type", PROBLEM_TYPE);
4340
out.append(',');
44-
appendStringField(out, "title", PROBLEM_TITLE);
41+
JsonStrings.appendStringField(out, "title", PROBLEM_TITLE);
4542
out.append(',');
4643
appendIntField(out, "status", PROBLEM_STATUS);
4744
out.append(',');
48-
appendStringField(out, "detail", error.message());
45+
JsonStrings.appendStringField(out, "detail", error.message());
4946
out.append(',');
50-
appendStringField(out, "pointer", error.pointer());
47+
JsonStrings.appendStringField(out, "pointer", error.pointer());
5148
out.append(',');
52-
appendStringField(out, "keyword", error.keyword());
49+
JsonStrings.appendStringField(out, "keyword", error.keyword());
5350
out.append('}');
5451
return out.toString();
5552
}
5653

57-
private static void appendStringField(StringBuilder out, String name, String value) {
58-
out.append('"').append(name).append("\":\"");
59-
appendEscaped(out, value);
60-
out.append('"');
61-
}
62-
6354
private static void appendIntField(StringBuilder out, String name, int value) {
6455
out.append('"').append(name).append("\":").append(value);
6556
}
66-
67-
/**
68-
* Appends {@code value} to {@code out} with JSON-string escaping applied. Handles the six
69-
* mandatory escape sequences and emits {@code &#92;uXXXX} for control characters below {@link
70-
* #FIRST_PRINTABLE_ASCII}.
71-
*/
72-
private static void appendEscaped(StringBuilder out, String value) {
73-
for (int i = 0; i < value.length(); i++) {
74-
char c = value.charAt(i);
75-
switch (c) {
76-
case '\\' -> out.append("\\\\");
77-
case '"' -> out.append("\\\"");
78-
case '\n' -> out.append("\\n");
79-
case '\r' -> out.append("\\r");
80-
case '\t' -> out.append("\\t");
81-
default -> appendUnicodeOrLiteral(out, c);
82-
}
83-
}
84-
}
85-
86-
private static void appendUnicodeOrLiteral(StringBuilder out, char c) {
87-
if (c < FIRST_PRINTABLE_ASCII) {
88-
out.append(String.format("\\u%04x", (int) c));
89-
} else {
90-
out.append(c);
91-
}
92-
}
9357
}

0 commit comments

Comments
 (0)