Skip to content

Commit 4eb544f

Browse files
authored
Updates to Stream Serialization Libraries (Azure#33270)
1 parent 40c8524 commit 4eb544f

File tree

10 files changed

+214
-154
lines changed

10 files changed

+214
-154
lines changed

sdk/core/azure-core/src/main/java/com/azure/core/implementation/http/rest/ReflectionSerializable.java

Lines changed: 103 additions & 107 deletions
Large diffs are not rendered by default.

sdk/core/azure-core/src/main/java/com/azure/core/util/BinaryData.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,9 @@ public static BinaryData fromByteBuffer(ByteBuffer data) {
566566
*
567567
* <!-- src_embed com.azure.core.util.BinaryData.fromListByteBuffer#List -->
568568
* <pre>
569-
* final List&lt;ByteBuffer&gt; data = Stream.of("Some ", "data")
570-
* .map(s -> ByteBuffer.wrap(s.getBytes(StandardCharsets.UTF_8)))
571-
* .collect(Collectors.toList());
569+
* final List&lt;ByteBuffer&gt; data = Stream.of&#40;&quot;Some &quot;, &quot;data&quot;&#41;
570+
* .map&#40;s -&gt; ByteBuffer.wrap&#40;s.getBytes&#40;StandardCharsets.UTF_8&#41;&#41;&#41;
571+
* .collect&#40;Collectors.toList&#40;&#41;&#41;;
572572
* BinaryData binaryData = BinaryData.fromListByteBuffer&#40;data&#41;;
573573
* System.out.println&#40;binaryData&#41;;
574574
* </pre>

sdk/core/azure-json-gson/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
### Bugs Fixed
1010

11+
- Fixed a bug where `GsonJsonReader.bufferObject` would throw an exception if the starting `JsonToken` was a field name
12+
not followed by a start object. Buffering now supports field starting points.
13+
1114
### Other Changes
1215

1316
## 1.0.0-beta.1 (2022-09-22)

sdk/core/azure-json-gson/src/main/java/com/azure/json/gson/GsonJsonReader.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,9 @@ public void skipChildren() throws IOException {
233233

234234
@Override
235235
public JsonReader bufferObject() throws IOException {
236-
if (currentToken == JsonToken.START_OBJECT
237-
|| (currentToken == JsonToken.FIELD_NAME && nextToken() == JsonToken.START_OBJECT)) {
236+
if (currentToken == JsonToken.START_OBJECT || currentToken == JsonToken.FIELD_NAME) {
238237
consumed = true;
239-
StringBuilder bufferedObject = new StringBuilder();
240-
readChildren(bufferedObject);
241-
String json = bufferedObject.toString();
238+
String json = readRemainingFieldsAsJsonObject();
242239
return new GsonJsonReader(new StringReader(json), true, null, json, nonNumericNumbersSupported);
243240
} else {
244241
throw new IllegalStateException("Cannot buffer a JSON object from a non-object, non-field name "

sdk/core/azure-json-reflect/src/main/java/com/azure/json/reflect/GsonJsonReader.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,12 +301,9 @@ public void skipChildren() throws IOException {
301301

302302
@Override
303303
public JsonReader bufferObject() throws IOException {
304-
if (currentToken == JsonToken.START_OBJECT
305-
|| (currentToken == JsonToken.FIELD_NAME && nextToken() == JsonToken.START_OBJECT)) {
304+
if (currentToken == JsonToken.START_OBJECT || currentToken == JsonToken.FIELD_NAME) {
306305
consumed = true;
307-
StringBuilder bufferedObject = new StringBuilder();
308-
readChildren(bufferedObject);
309-
String json = bufferedObject.toString();
306+
String json = readRemainingFieldsAsJsonObject();
310307
return new GsonJsonReader(new StringReader(json), true, null, json, nonNumericNumbersSupported);
311308
} else {
312309
throw new IllegalStateException("Cannot buffer a JSON object from a non-object, non-field name "

sdk/core/azure-json-reflect/src/main/java/com/azure/json/reflect/JacksonJsonReader.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,8 @@ public void skipChildren() throws IOException {
232232
@Override
233233
public JsonReader bufferObject() throws IOException {
234234
JsonToken currentToken = currentToken();
235-
if (currentToken == JsonToken.START_OBJECT
236-
|| (currentToken == JsonToken.FIELD_NAME && nextToken() == JsonToken.START_OBJECT)) {
237-
StringBuilder bufferedObject = new StringBuilder();
238-
readChildren(bufferedObject);
239-
String json = bufferedObject.toString();
235+
if (currentToken == JsonToken.START_OBJECT || currentToken == JsonToken.FIELD_NAME) {
236+
String json = readRemainingFieldsAsJsonObject();
240237
return new JacksonJsonReader(new StringReader(json), true, null, json, nonNumericNumbersSupported);
241238
} else {
242239
throw new IllegalStateException("Cannot buffer a JSON object from a non-object, non-field name "

sdk/core/azure-json/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@
44

55
### Features Added
66

7+
- Add `JsonReader.readRemainingFieldsAsJsonObject` to support reading the remainder of a JSON object from a field starting
8+
point.
9+
710
### Breaking Changes
811

912
### Bugs Fixed
1013

14+
- Fixed a bug where the default `JsonReader` would throw an exception if the starting `JsonToken` was a field name
15+
not followed by a start object when calling `bufferObject`. Buffering now supports field starting points.
16+
1117
### Other Changes
1218

1319
## 1.0.0-beta.1 (2022-09-22)

sdk/core/azure-json/src/main/java/com/azure/json/JsonReader.java

Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.util.LinkedList;
1212
import java.util.List;
1313
import java.util.Map;
14+
import java.util.Objects;
1415

1516
/**
1617
* Reads a JSON encoded value as a stream of tokens.
@@ -233,8 +234,12 @@ public final <T> T getNullable(ReadValueCallback<JsonReader, T> nonNullGetter) t
233234
* Reads and returns the current JSON object the {@link JsonReader} is pointing to. This will mutate the current
234235
* location of this {@link JsonReader}.
235236
* <p>
236-
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} followed by
237-
* {@link JsonToken#START_OBJECT} an {@link IllegalStateException} will be thrown.
237+
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} an
238+
* {@link IllegalStateException} will be thrown.
239+
* <p>
240+
* If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this will create a JSON object where the first
241+
* JSON field is the {@link #currentToken()} field, meaning this can be called from the middle of a JSON object to
242+
* create a new JSON object with only a subset of fields (those remaining from when the method is called).
238243
* <p>
239244
* The returned {@link JsonReader} is able to be {@link #reset()} to replay the underlying JSON stream.
240245
*
@@ -268,37 +273,87 @@ public final <T> T getNullable(ReadValueCallback<JsonReader, T> nonNullGetter) t
268273
* Recursively reads the JSON token sub-stream if the current token is either {@link JsonToken#START_ARRAY} or
269274
* {@link JsonToken#START_OBJECT}.
270275
* <p>
271-
* If the current token isn't the beginning of an array or object this method is a no-op.
276+
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY} nothing will
277+
* be read.
272278
*
273279
* @return The raw textual value of the JSON token sub-stream.
274280
* @throws IOException If the children cannot be read.
275281
*/
276282
public final String readChildren() throws IOException {
277-
return readChildrenInternal(new StringBuilder()).toString();
283+
return readInternal(new StringBuilder(), true, false).toString();
278284
}
279285

280286
/**
281287
* Recursively reads the JSON token sub-stream if the current token is either {@link JsonToken#START_ARRAY} or
282288
* {@link JsonToken#START_OBJECT} into the passed {@link StringBuilder}.
283289
* <p>
284-
* If the current token isn't the beginning of an array or object this method is a no-op.
290+
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#START_ARRAY} nothing will
291+
* be read.
285292
*
286293
* @param buffer The {@link StringBuilder} where the read sub-stream will be written.
294+
* @throws NullPointerException If {@code buffer} is null.
287295
* @throws IOException If the children cannot be read.
288296
*/
289297
public final void readChildren(StringBuilder buffer) throws IOException {
290-
readChildrenInternal(buffer);
298+
readInternal(buffer, true, false);
299+
}
300+
301+
/**
302+
* Reads the remaining fields in the current JSON object as a JSON object.
303+
* <p>
304+
* If the {@link #currentToken()} is {@link JsonToken#START_OBJECT} this functions the same as
305+
* {@link #readChildren()}. If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this creates a JSON
306+
* object where the first field is the current field and reads the remaining fields in the JSON object.
307+
* <p>
308+
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} nothing will
309+
* be read.
310+
*
311+
* @return The raw textual value of the remaining JSON fields.
312+
* @throws IOException If the remaining JSON fields cannot be read.
313+
*/
314+
public final String readRemainingFieldsAsJsonObject() throws IOException {
315+
return readInternal(new StringBuilder(), false, true).toString();
316+
}
317+
318+
/**
319+
* Reads the remaining fields in the current JSON object as a JSON object.
320+
* <p>
321+
* If the {@link #currentToken()} is {@link JsonToken#START_OBJECT} this functions the same as
322+
* {@link #readChildren(StringBuilder)}. If the {@link #currentToken()} is {@link JsonToken#FIELD_NAME} this creates
323+
* a JSON object where the first field is the current field and reads the remaining fields in the JSON object.
324+
* <p>
325+
* If the {@link #currentToken()} isn't {@link JsonToken#START_OBJECT} or {@link JsonToken#FIELD_NAME} nothing will
326+
* be read.
327+
*
328+
* @param buffer The {@link StringBuilder} where the remaining JSON fields will be written.
329+
* @throws NullPointerException If {@code buffer} is null.
330+
* @throws IOException If the remaining JSON fields cannot be read.
331+
*/
332+
public final void readRemainingFieldsAsJsonObject(StringBuilder buffer) throws IOException {
333+
readInternal(buffer, false, true);
291334
}
292335

293-
private StringBuilder readChildrenInternal(StringBuilder buffer) throws IOException {
336+
private StringBuilder readInternal(StringBuilder buffer, boolean canStartAtArray, boolean canStartAtFieldName)
337+
throws IOException {
338+
Objects.requireNonNull(buffer, "The 'buffer' used to read the JSON object cannot be null.");
339+
294340
JsonToken token = currentToken();
295341

296-
// Not pointing to an array or object start, no-op.
297-
if (!isStartArrayOrObject(token)) {
342+
boolean canRead = (token == JsonToken.START_OBJECT)
343+
|| (canStartAtArray && token == JsonToken.START_ARRAY)
344+
|| (canStartAtFieldName && token == JsonToken.FIELD_NAME);
345+
346+
// Not a valid starting poing.
347+
if (!canRead) {
298348
return buffer;
299349
}
300350

301-
buffer.append(getText());
351+
if (token == JsonToken.FIELD_NAME) {
352+
buffer.append("{\"").append(getText()).append("\":");
353+
token = nextToken();
354+
}
355+
356+
appendJson(buffer, token);
302357

303358
// Initial array or object depth is 1.
304359
int depth = 1;
@@ -328,18 +383,31 @@ private StringBuilder readChildrenInternal(StringBuilder buffer) throws IOExcept
328383
buffer.append(',');
329384
}
330385

331-
if (token == JsonToken.FIELD_NAME) {
332-
buffer.append("\"").append(getFieldName()).append("\":");
333-
} else if (token == JsonToken.STRING) {
334-
buffer.append("\"").append(getString()).append("\"");
335-
} else {
336-
buffer.append(getText());
337-
}
386+
appendJson(buffer, token);
338387
}
339388

340389
return buffer;
341390
}
342391

392+
/**
393+
* Convenience method to read a JSON element into a buffer.
394+
*
395+
* @param buffer The buffer where the JSON element value will be written.
396+
* @param token The type of the JSON element.
397+
* @throws IOException If an error occurs while reading the JSON element.
398+
*/
399+
private void appendJson(StringBuilder buffer, JsonToken token) throws IOException {
400+
// TODO (alzimmer): Think of making this a protected method. This will allow for optimizations such as where
401+
// Jackson can read text directly into a StringBuilder which removes a String copy.
402+
if (token == JsonToken.FIELD_NAME) {
403+
buffer.append("\"").append(getFieldName()).append("\":");
404+
} else if (token == JsonToken.STRING) {
405+
buffer.append("\"").append(getString()).append("\"");
406+
} else {
407+
buffer.append(getText());
408+
}
409+
}
410+
343411
/**
344412
* Reads a JSON object.
345413
* <p>

sdk/core/azure-json/src/main/java/com/azure/json/implementation/DefaultJsonReader.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,11 +156,8 @@ public void skipChildren() throws IOException {
156156
@Override
157157
public JsonReader bufferObject() throws IOException {
158158
JsonToken currentToken = currentToken();
159-
if (currentToken == JsonToken.START_OBJECT
160-
|| (currentToken == JsonToken.FIELD_NAME && nextToken() == JsonToken.START_OBJECT)) {
161-
StringBuilder bufferedObject = new StringBuilder();
162-
readChildren(bufferedObject);
163-
String json = bufferedObject.toString();
159+
if (currentToken == JsonToken.START_OBJECT || currentToken == JsonToken.FIELD_NAME) {
160+
String json = readRemainingFieldsAsJsonObject();
164161
try {
165162
return new DefaultJsonReader(FACTORY.createParser(json), true, null, json, nonNumericNumbersSupported);
166163
} catch (IOException ex) {

sdk/core/azure-json/src/test/java/com/azure/json/contract/JsonReaderContractTests.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,8 @@ public void bufferObject(String json, int nextCount) throws IOException {
582582

583583
private static Stream<Arguments> bufferObjectSupplier() {
584584
return Stream.of(
585-
// Arguments.of("{\"test\":\"test\"}", 1),
586-
Arguments.of("{\"outerfield\":{\"test\":\"test\"}}", 2)
585+
Arguments.of("{\"test\":\"test\"}", 1),
586+
Arguments.of("{\"outerfield\":{\"test\":\"test\"}}", 3)
587587
);
588588
}
589589

@@ -606,11 +606,10 @@ private static Stream<Arguments> bufferObjectIllegalStateSupplier() {
606606
Arguments.of("null", 1),
607607
Arguments.of("true", 1),
608608
Arguments.of("\"hello\"", 1),
609-
Arguments.of("{\"outerfield\": []}", 2),
610-
Arguments.of("{\"outerfield\": 12}", 2),
611-
Arguments.of("{\"outerfield\": null}", 2),
612-
Arguments.of("{\"outerfield\": true}", 2),
613-
Arguments.of("{\"outerfield\": \"hello\"}", 2)
609+
Arguments.of("{\"outerfield\": 12}", 3),
610+
Arguments.of("{\"outerfield\": null}", 3),
611+
Arguments.of("{\"outerfield\": true}", 3),
612+
Arguments.of("{\"outerfield\": \"hello\"}", 3)
614613
);
615614
}
616615

0 commit comments

Comments
 (0)