[vm_service] Decode incoming JSON in one step instead of two

Currently we decode incoming data as a string, then parse the string as
JSON.

We can do it in one step by fusing a `Utf8Decoder` with a `JsonDecoder`,
which will be faster.

A difference between `jsonDecode` and the fused version is that
`jsonDecode` returns `dynamic`, the fused version returns `Object?`.

To keep changes as small as possible we cast the return value of the
fused verison to `dynamic` in this CL.

However using `dynamic` values is often slower than type casting it to
the right type and then using it. So it might make sense to cast it to
something like `Map<String, dynamic>` in a separate CL.

Change-Id: I76de535a72ae5532db5a27e543929519c48c701c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/371080
Commit-Queue: Ömer Ağacan <omersa@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
diff --git a/pkg/vm_service/lib/src/vm_service.dart b/pkg/vm_service/lib/src/vm_service.dart
index c227706..31fe1af 100644
--- a/pkg/vm_service/lib/src/vm_service.dart
+++ b/pkg/vm_service/lib/src/vm_service.dart
@@ -12,7 +12,8 @@
 // ignore_for_file: overridden_fields
 
 import 'dart:async';
-import 'dart:convert' show base64, jsonDecode, jsonEncode, utf8;
+import 'dart:convert'
+    show base64, jsonDecode, JsonDecoder, jsonEncode, utf8, Utf8Decoder;
 import 'dart:typed_data';
 
 export 'snapshot_graph.dart'
@@ -1825,11 +1826,11 @@
     final int dataOffset = bytes.getUint32(0, Endian.little);
     final metaLength = dataOffset - metaOffset;
     final dataLength = bytes.lengthInBytes - dataOffset;
-    final meta = utf8.decode(Uint8List.view(
-        bytes.buffer, bytes.offsetInBytes + metaOffset, metaLength));
+    final decoder = (const Utf8Decoder()).fuse(const JsonDecoder());
+    final map = decoder.convert(Uint8List.view(
+        bytes.buffer, bytes.offsetInBytes + metaOffset, metaLength)) as dynamic;
     final data = ByteData.view(
         bytes.buffer, bytes.offsetInBytes + dataOffset, dataLength);
-    final map = jsonDecode(meta)!;
     if (map['method'] == 'streamNotify') {
       final streamId = map['params']['streamId'];
       final event = map['params']['event'];
diff --git a/pkg/vm_service/tool/dart/generate_dart_client.dart b/pkg/vm_service/tool/dart/generate_dart_client.dart
index cf55d82..986630d 100644
--- a/pkg/vm_service/tool/dart/generate_dart_client.dart
+++ b/pkg/vm_service/tool/dart/generate_dart_client.dart
@@ -20,7 +20,8 @@
 // ignore_for_file: overridden_fields
 
 import 'dart:async';
-import 'dart:convert' show base64, jsonDecode, jsonEncode, utf8;
+import 'dart:convert'
+    show base64, jsonDecode, JsonDecoder, jsonEncode, utf8, Utf8Decoder;
 import 'dart:typed_data';
 
 export 'snapshot_graph.dart' show HeapSnapshotClass,
@@ -148,11 +149,11 @@
     final int dataOffset = bytes.getUint32(0, Endian.little);
     final metaLength = dataOffset - metaOffset;
     final dataLength = bytes.lengthInBytes - dataOffset;
-    final meta = utf8.decode(Uint8List.view(
-        bytes.buffer, bytes.offsetInBytes + metaOffset, metaLength));
+    final decoder = (const Utf8Decoder()).fuse(const JsonDecoder());
+    final map = decoder.convert(Uint8List.view(
+        bytes.buffer, bytes.offsetInBytes + metaOffset, metaLength)) as dynamic;
     final data = ByteData.view(
         bytes.buffer, bytes.offsetInBytes + dataOffset, dataLength);
-    final map = jsonDecode(meta)!;
     if (map['method'] == 'streamNotify') {
       final streamId = map['params']['streamId'];
       final event = map['params']['event'];