use LazyMap in extension constructors
diff --git a/pkgs/dart_model/lib/dart_model.dart b/pkgs/dart_model/lib/dart_model.dart
index 1358b0b..be4c7bf 100644
--- a/pkgs/dart_model/lib/dart_model.dart
+++ b/pkgs/dart_model/lib/dart_model.dart
@@ -4,4 +4,5 @@
 
 export 'src/dart_model.dart';
 export 'src/json.dart';
+export 'src/json_buffer.dart' show LazyMap;
 export 'src/json_changes.dart';
diff --git a/pkgs/dart_model/lib/src/dart_model.g.dart b/pkgs/dart_model/lib/src/dart_model.g.dart
index 6e1e7c7..a21fd71 100644
--- a/pkgs/dart_model/lib/src/dart_model.g.dart
+++ b/pkgs/dart_model/lib/src/dart_model.g.dart
@@ -1,13 +1,20 @@
 // This file is generated. To make changes edit schemas/*.schema.json
 // then run from the repo root: dart tool/dart_model_generator/bin/main.dart
 
+import 'json_buffer.dart' show LazyMap;
+
 /// An augmentation to Dart code. TODO(davidmorgan): this is a placeholder.
 extension type Augmentation.fromJson(Map<String, Object?> node) {
   Augmentation({
     String? code,
-  }) : this.fromJson({
-          if (code != null) 'code': code,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (code != null) 'code',
+            ],
+            (key) => switch (key) {
+                  'code' => code,
+                  _ => null,
+                }));
 
   /// Augmentation code.
   String get code => node['code'] as String;
@@ -17,9 +24,14 @@
 extension type MetadataAnnotation.fromJson(Map<String, Object?> node) {
   MetadataAnnotation({
     QualifiedName? type,
-  }) : this.fromJson({
-          if (type != null) 'type': type,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (type != null) 'type',
+            ],
+            (key) => switch (key) {
+                  'type' => type,
+                  _ => null,
+                }));
 
   /// The type of the annotation.
   QualifiedName get type => node['type'] as QualifiedName;
@@ -31,12 +43,18 @@
     List<MetadataAnnotation>? metadataAnnotations,
     Map<String, Member>? members,
     Properties? properties,
-  }) : this.fromJson({
-          if (metadataAnnotations != null)
-            'metadataAnnotations': metadataAnnotations,
-          if (members != null) 'members': members,
-          if (properties != null) 'properties': properties,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (metadataAnnotations != null) 'metadataAnnotations',
+              if (members != null) 'members',
+              if (properties != null) 'properties',
+            ],
+            (key) => switch (key) {
+                  'metadataAnnotations' => metadataAnnotations,
+                  'members' => members,
+                  'properties' => properties,
+                  _ => null,
+                }));
 
   /// The metadata annotations attached to this iterface.
   List<MetadataAnnotation> get metadataAnnotations =>
@@ -53,9 +71,14 @@
 extension type Library.fromJson(Map<String, Object?> node) {
   Library({
     Map<String, Interface>? scopes,
-  }) : this.fromJson({
-          if (scopes != null) 'scopes': scopes,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (scopes != null) 'scopes',
+            ],
+            (key) => switch (key) {
+                  'scopes' => scopes,
+                  _ => null,
+                }));
 
   /// Scopes by name.
   Map<String, Interface> get scopes => (node['scopes'] as Map).cast();
@@ -65,9 +88,14 @@
 extension type Member.fromJson(Map<String, Object?> node) {
   Member({
     Properties? properties,
-  }) : this.fromJson({
-          if (properties != null) 'properties': properties,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (properties != null) 'properties',
+            ],
+            (key) => switch (key) {
+                  'properties' => properties,
+                  _ => null,
+                }));
 
   /// The properties of this member.
   Properties get properties => node['properties'] as Properties;
@@ -77,9 +105,14 @@
 extension type Model.fromJson(Map<String, Object?> node) {
   Model({
     Map<String, Library>? uris,
-  }) : this.fromJson({
-          if (uris != null) 'uris': uris,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (uris != null) 'uris',
+            ],
+            (key) => switch (key) {
+                  'uris' => uris,
+                  _ => null,
+                }));
 
   /// Libraries by URI.
   Map<String, Library> get uris => (node['uris'] as Map).cast();
@@ -94,9 +127,14 @@
 extension type NullableType.fromJson(Map<String, Object?> node) {
   NullableType({
     StaticType? inner,
-  }) : this.fromJson({
-          if (inner != null) 'inner': inner,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (inner != null) 'inner',
+            ],
+            (key) => switch (key) {
+                  'inner' => inner,
+                  _ => null,
+                }));
   StaticType get inner => node['inner'] as StaticType;
 }
 
@@ -109,14 +147,24 @@
     bool? isField,
     bool? isMethod,
     bool? isStatic,
-  }) : this.fromJson({
-          if (isAbstract != null) 'isAbstract': isAbstract,
-          if (isClass != null) 'isClass': isClass,
-          if (isGetter != null) 'isGetter': isGetter,
-          if (isField != null) 'isField': isField,
-          if (isMethod != null) 'isMethod': isMethod,
-          if (isStatic != null) 'isStatic': isStatic,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (isAbstract != null) 'isAbstract',
+              if (isClass != null) 'isClass',
+              if (isGetter != null) 'isGetter',
+              if (isField != null) 'isField',
+              if (isMethod != null) 'isMethod',
+              if (isStatic != null) 'isStatic',
+            ],
+            (key) => switch (key) {
+                  'isAbstract' => isAbstract,
+                  'isClass' => isClass,
+                  'isGetter' => isGetter,
+                  'isField' => isField,
+                  'isMethod' => isMethod,
+                  'isStatic' => isStatic,
+                  _ => null,
+                }));
 
   /// Whether the entity is abstract, meaning it has no definition.
   bool get isAbstract => node['isAbstract'] as bool;
@@ -146,9 +194,14 @@
 extension type Query.fromJson(Map<String, Object?> node) {
   Query({
     QualifiedName? target,
-  }) : this.fromJson({
-          if (target != null) 'target': target,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (target != null) 'target',
+            ],
+            (key) => switch (key) {
+                  'target' => target,
+                  _ => null,
+                }));
 
   /// The class to query about.
   QualifiedName get target => node['target'] as QualifiedName;
diff --git a/pkgs/dart_model/lib/src/json_buffer.dart b/pkgs/dart_model/lib/src/json_buffer.dart
index 16a6b3c..85e1350 100644
--- a/pkgs/dart_model/lib/src/json_buffer.dart
+++ b/pkgs/dart_model/lib/src/json_buffer.dart
@@ -16,6 +16,13 @@
 
   LazyMap(Iterable<String> keys, this.lookup) : _keys = keys.toList();
 
+  const LazyMap.empty()
+      : _keys = const [],
+        lookup = _emptyLookup;
+
+  /// So we can create a const empty map.
+  static Object? _emptyLookup(_) => null;
+
   @override
   Iterable<String> get keys => _keys;
 
diff --git a/pkgs/macro_service/lib/src/macro_service.g.dart b/pkgs/macro_service/lib/src/macro_service.g.dart
index 9e72112..c182f30 100644
--- a/pkgs/macro_service/lib/src/macro_service.g.dart
+++ b/pkgs/macro_service/lib/src/macro_service.g.dart
@@ -8,10 +8,16 @@
   AugmentRequest({
     int? phase,
     QualifiedName? target,
-  }) : this.fromJson({
-          if (phase != null) 'phase': phase,
-          if (target != null) 'target': target,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (phase != null) 'phase',
+              if (target != null) 'target',
+            ],
+            (key) => switch (key) {
+                  'phase' => phase,
+                  'target' => target,
+                  _ => null,
+                }));
 
   /// Which phase to run: 1, 2 or 3.
   int get phase => node['phase'] as int;
@@ -24,9 +30,14 @@
 extension type AugmentResponse.fromJson(Map<String, Object?> node) {
   AugmentResponse({
     List<Augmentation>? augmentations,
-  }) : this.fromJson({
-          if (augmentations != null) 'augmentations': augmentations,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (augmentations != null) 'augmentations',
+            ],
+            (key) => switch (key) {
+                  'augmentations' => augmentations,
+                  _ => null,
+                }));
 
   /// The augmentations.
   List<Augmentation> get augmentations =>
@@ -37,9 +48,14 @@
 extension type ErrorResponse.fromJson(Map<String, Object?> node) {
   ErrorResponse({
     String? error,
-  }) : this.fromJson({
-          if (error != null) 'error': error,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (error != null) 'error',
+            ],
+            (key) => switch (key) {
+                  'error' => error,
+                  _ => null,
+                }));
 
   /// The error.
   String get error => node['error'] as String;
@@ -49,9 +65,14 @@
 extension type HostEndpoint.fromJson(Map<String, Object?> node) {
   HostEndpoint({
     int? port,
-  }) : this.fromJson({
-          if (port != null) 'port': port,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (port != null) 'port',
+            ],
+            (key) => switch (key) {
+                  'port' => port,
+                  _ => null,
+                }));
 
   /// TCP port to connect to.
   int get port => node['port'] as int;
@@ -68,12 +89,12 @@
 extension type HostRequest.fromJson(Map<String, Object?> node) {
   static HostRequest augmentRequest(
     AugmentRequest augmentRequest, {
-    required int id,
+    int? id,
   }) =>
       HostRequest.fromJson({
         'type': 'AugmentRequest',
         'value': augmentRequest,
-        'id': id,
+        if (id != null) 'id': id,
       });
   HostRequestType get type {
     switch (node['type'] as String) {
@@ -99,9 +120,14 @@
 extension type MacroDescription.fromJson(Map<String, Object?> node) {
   MacroDescription({
     List<int>? runsInPhases,
-  }) : this.fromJson({
-          if (runsInPhases != null) 'runsInPhases': runsInPhases,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (runsInPhases != null) 'runsInPhases',
+            ],
+            (key) => switch (key) {
+                  'runsInPhases' => runsInPhases,
+                  _ => null,
+                }));
 
   /// Phases that the macro runs in: 1, 2 and/or 3.
   List<int> get runsInPhases => (node['runsInPhases'] as List).cast();
@@ -111,16 +137,21 @@
 extension type MacroStartedRequest.fromJson(Map<String, Object?> node) {
   MacroStartedRequest({
     MacroDescription? macroDescription,
-  }) : this.fromJson({
-          if (macroDescription != null) 'macroDescription': macroDescription,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (macroDescription != null) 'macroDescription',
+            ],
+            (key) => switch (key) {
+                  'macroDescription' => macroDescription,
+                  _ => null,
+                }));
   MacroDescription get macroDescription =>
       node['macroDescription'] as MacroDescription;
 }
 
 /// Host's response to a [MacroStartedRequest].
 extension type MacroStartedResponse.fromJson(Map<String, Object?> node) {
-  MacroStartedResponse() : this.fromJson({});
+  MacroStartedResponse() : this.fromJson(const LazyMap.empty());
 }
 
 enum MacroRequestType {
@@ -135,21 +166,21 @@
 extension type MacroRequest.fromJson(Map<String, Object?> node) {
   static MacroRequest macroStartedRequest(
     MacroStartedRequest macroStartedRequest, {
-    required int id,
+    int? id,
   }) =>
       MacroRequest.fromJson({
         'type': 'MacroStartedRequest',
         'value': macroStartedRequest,
-        'id': id,
+        if (id != null) 'id': id,
       });
   static MacroRequest queryRequest(
     QueryRequest queryRequest, {
-    required int id,
+    int? id,
   }) =>
       MacroRequest.fromJson({
         'type': 'QueryRequest',
         'value': queryRequest,
-        'id': id,
+        if (id != null) 'id': id,
       });
   MacroRequestType get type {
     switch (node['type'] as String) {
@@ -184,9 +215,14 @@
 extension type QueryRequest.fromJson(Map<String, Object?> node) {
   QueryRequest({
     Query? query,
-  }) : this.fromJson({
-          if (query != null) 'query': query,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (query != null) 'query',
+            ],
+            (key) => switch (key) {
+                  'query' => query,
+                  _ => null,
+                }));
   Query get query => node['query'] as Query;
 }
 
@@ -194,9 +230,14 @@
 extension type QueryResponse.fromJson(Map<String, Object?> node) {
   QueryResponse({
     Model? model,
-  }) : this.fromJson({
-          if (model != null) 'model': model,
-        });
+  }) : this.fromJson(LazyMap(
+            [
+              if (model != null) 'model',
+            ],
+            (key) => switch (key) {
+                  'model' => model,
+                  _ => null,
+                }));
   Model get model => node['model'] as Model;
 }
 
@@ -214,39 +255,39 @@
 extension type Response.fromJson(Map<String, Object?> node) {
   static Response augmentResponse(
     AugmentResponse augmentResponse, {
-    required int requestId,
+    int? requestId,
   }) =>
       Response.fromJson({
         'type': 'AugmentResponse',
         'value': augmentResponse,
-        'requestId': requestId,
+        if (requestId != null) 'requestId': requestId,
       });
   static Response errorResponse(
     ErrorResponse errorResponse, {
-    required int requestId,
+    int? requestId,
   }) =>
       Response.fromJson({
         'type': 'ErrorResponse',
         'value': errorResponse,
-        'requestId': requestId,
+        if (requestId != null) 'requestId': requestId,
       });
   static Response macroStartedResponse(
     MacroStartedResponse macroStartedResponse, {
-    required int requestId,
+    int? requestId,
   }) =>
       Response.fromJson({
         'type': 'MacroStartedResponse',
         'value': macroStartedResponse,
-        'requestId': requestId,
+        if (requestId != null) 'requestId': requestId,
       });
   static Response queryResponse(
     QueryResponse queryResponse, {
-    required int requestId,
+    int? requestId,
   }) =>
       Response.fromJson({
         'type': 'QueryResponse',
         'value': queryResponse,
-        'requestId': requestId,
+        if (requestId != null) 'requestId': requestId,
       });
   ResponseType get type {
     switch (node['type'] as String) {
diff --git a/tool/dart_model_generator/lib/generate_dart_model.dart b/tool/dart_model_generator/lib/generate_dart_model.dart
index 2089b13..6aad0c6 100644
--- a/tool/dart_model_generator/lib/generate_dart_model.dart
+++ b/tool/dart_model_generator/lib/generate_dart_model.dart
@@ -17,24 +17,23 @@
 /// They have a `fromJson` constructor that takes that JSON, and a no-name
 /// constructor that builds it.
 void run() {
-  File('pkgs/dart_model/lib/src/dart_model.g.dart').writeAsStringSync(
-      generate(File('schemas/dart_model.schema.json').readAsStringSync()));
+  File('pkgs/dart_model/lib/src/dart_model.g.dart').writeAsStringSync(generate(
+      File('schemas/dart_model.schema.json').readAsStringSync(),
+      directives: const ["import 'json_buffer.dart' show LazyMap;"]));
   File('pkgs/macro_service/lib/src/macro_service.g.dart').writeAsStringSync(
       generate(File('schemas/macro_service.schema.json').readAsStringSync(),
-          importDartModel: true));
+          directives: const ["import 'package:dart_model/dart_model.dart';"]));
 }
 
 /// Generates and returns code for [schemaJson].
 String generate(String schemaJson,
-    {bool importDartModel = false,
-    bool importMacroService = false,
-    String? dartModelJson}) {
+    {List<String> directives = const [], String? dartModelJson}) {
   final result = <String>[
     '// This file is generated. To make changes edit schemas/*.schema.json',
     '// then run from the repo root: '
         'dart tool/dart_model_generator/bin/main.dart',
     '',
-    if (importDartModel) "import 'package:dart_model/dart_model.dart';",
+    ...directives,
   ];
   final schema = JsonSchema.create(schemaJson,
       refProvider: LocalRefProvider(dartModelJson ??
@@ -95,17 +94,20 @@
   switch (definition.type) {
     case SchemaType.object:
       if (propertyMetadatas.isEmpty) {
-        result.writeln('  $name() : this.fromJson({});');
+        result.writeln('  $name() : this.fromJson(const LazyMap.empty());');
       } else {
         result.writeln('  $name({');
         for (final property in propertyMetadatas) {
-          result.writeParameter(property, definition);
+          result.writeParameter(property);
         }
-        result.writeln('}) : this.fromJson({');
+        result.writeln('}) : this.fromJson(LazyMap([');
         for (final property in propertyMetadatas) {
-          result.writeMapElement(property, definition);
+          result.writeKeyElement(property);
         }
-        result.writeln('});');
+        result.writeln('], (key) => switch (key) {');
+        propertyMetadatas.forEach(result.writeSwitchCase);
+        result.writeln('_ => null,'); // Default case
+        result.writeln('}));');
       }
     case SchemaType.string:
       result.writeln('$name(String string) : this.fromJson(string);');
@@ -176,7 +178,7 @@
     if (extraPropertyMetadatas.isNotEmpty) {
       result.writeln(', {');
       for (final property in extraPropertyMetadatas) {
-        result.writeParameter(property, definition);
+        result.writeParameter(property);
       }
       result.write('}');
     }
@@ -186,7 +188,7 @@
       ..writeln("'type': '$type',")
       ..writeln("'value': $lowerType,");
     for (final property in extraPropertyMetadatas) {
-      result.writeMapElement(property, definition);
+      result.writeMapElement(property);
     }
     result.writeln('});');
   }
@@ -241,7 +243,8 @@
           description: schema.schemaMap![r'$comment'] as String?,
           name: name,
           type: PropertyType.object,
-          elementTypeName: schemaName);
+          elementTypeName: schemaName,
+          isRequired: schema.propertyRequired(name));
     } else {
       throw UnsupportedError('Unsupported: $name $schema');
     }
@@ -250,24 +253,33 @@
   // Otherwise, it's a schema with a type.
   return switch (schema.type) {
     SchemaType.boolean => PropertyMetadata(
-        description: schema.description, name: name, type: PropertyType.bool),
+        description: schema.description,
+        name: name,
+        type: PropertyType.bool,
+        isRequired: schema.propertyRequired(name)),
     SchemaType.string => PropertyMetadata(
-        description: schema.description, name: name, type: PropertyType.string),
+        description: schema.description,
+        name: name,
+        type: PropertyType.string,
+        isRequired: schema.propertyRequired(name)),
     SchemaType.integer => PropertyMetadata(
         description: schema.description,
         name: name,
-        type: PropertyType.integer),
+        type: PropertyType.integer,
+        isRequired: schema.propertyRequired(name)),
     SchemaType.array => PropertyMetadata(
         description: schema.description,
         name: name,
         type: PropertyType.list,
-        elementTypeName: _readRefNameOrType(schema, 'items')),
+        elementTypeName: _readRefNameOrType(schema, 'items'),
+        isRequired: schema.propertyRequired(name)),
     SchemaType.object => PropertyMetadata(
         description: schema.description,
         name: name,
         type: PropertyType.map,
         // `additionalProperties` should be a type specified with a `$ref`.
-        elementTypeName: _readRefNameOrType(schema, 'additionalProperties')),
+        elementTypeName: _readRefNameOrType(schema, 'additionalProperties'),
+        isRequired: schema.propertyRequired(name)),
     _ => throw UnsupportedError('Unsupported schema type: ${schema.type}'),
   };
 }
@@ -311,12 +323,15 @@
   String name;
   PropertyType type;
   String? elementTypeName;
+  bool isRequired;
 
-  PropertyMetadata(
-      {this.description,
-      required this.name,
-      required this.type,
-      this.elementTypeName});
+  PropertyMetadata({
+    this.description,
+    required this.name,
+    required this.type,
+    this.elementTypeName,
+    required this.isRequired,
+  });
 }
 
 /// Loads referenced schemas.
@@ -355,13 +370,26 @@
     if (nullable) write('?');
   }
 
+  /// Writes an element in a `keys` list for a map containing [property],
+  /// assuming there is a variable in scope with the same name (usually, a
+  /// function parameter).
+  ///
+  /// If the property is not required, the element will be omitted if the
+  /// variable is null.
+  void writeKeyElement(PropertyMetadata property) {
+    if (!property.isRequired) {
+      write('if (${property.name} != null) ');
+    }
+    writeln("'${property.name}',");
+  }
+
   /// Writes a map element for [property], assuming there is a variable in
   /// scope with the same name (usually, a function parameter).
   ///
   /// If the property is not required, the element will be omitted if the
   /// variable is null.
-  void writeMapElement(PropertyMetadata property, JsonSchema schema) {
-    if (!schema.propertyRequired(property.name)) {
+  void writeMapElement(PropertyMetadata property) {
+    if (!property.isRequired) {
       write('if (${property.name} != null) ');
     }
     writeln("'${property.name}': ${property.name},");
@@ -371,10 +399,9 @@
   ///
   /// If the property is required, it will be non-nullable and marked as
   /// `required`, otherwise it will be nullable and optional.
-  void writeParameter(PropertyMetadata property, JsonSchema schema) {
-    var required = schema.propertyRequired(property.name);
-    if (required) write('required ');
-    writeType(property, nullable: !required);
+  void writeParameter(PropertyMetadata property) {
+    if (property.isRequired) write('required ');
+    writeType(property, nullable: !property.isRequired);
     writeln(' ${property.name},');
   }
 
@@ -402,4 +429,11 @@
     }
     writeln(';');
   }
+
+  /// Writes a switch case statement to read [property] by its name, assuming
+  /// there is a variable in scope with the same name (usually, a function
+  /// parameter).
+  void writeSwitchCase(PropertyMetadata property) {
+    writeln("'${property.name}' => ${property.name},");
+  }
 }
diff --git a/tool/dart_model_generator/test/generated_output_test.dart b/tool/dart_model_generator/test/generated_output_test.dart
index cb989e3..277b8ba 100644
--- a/tool/dart_model_generator/test/generated_output_test.dart
+++ b/tool/dart_model_generator/test/generated_output_test.dart
@@ -13,7 +13,13 @@
     test('$package output is up to date', () {
       final expected = dart_model_generator.generate(
           File('../../schemas/$package.schema.json').readAsStringSync(),
-          importDartModel: package == 'macro_service',
+          directives: switch (package) {
+            'dart_model' => const ["import 'json_buffer.dart' show LazyMap;"],
+            'macro_service' => const [
+                "import 'package:dart_model/dart_model.dart';"
+              ],
+            _ => const [],
+          },
           dartModelJson:
               File('../../schemas/dart_model.schema.json').readAsStringSync());
       final actual = File('../../pkgs/$package/lib/src/$package.g.dart')