proto3 json support (#274)


diff --git a/protobuf/CHANGELOG.md b/protobuf/CHANGELOG.md
index fba6f37..aee700b 100644
--- a/protobuf/CHANGELOG.md
+++ b/protobuf/CHANGELOG.md
@@ -1,7 +1,14 @@
 ## 0.14.0
 
+* Support for proto3 json (json with field names as keys) 
+  - encoding and decoding.
+  - Support for well-known types.
+  - Use `GeneratedMessage.toProto3Json()` to encode and `GeneratedMessage.mergeFromProto3Json(json)`
+    to decode.
+
 * `FieldInfo` objects have a new getter `.protoName` that gives the non-camel-case name of the field
   as in the `.proto`-file.
+
 * **Breaking**: The field-adder methods on `BuilderInfo` now takes only named optional arguments.
   To migrate, update `protoc_plugin` to version 18.0.0 or higher.
 * The field-adder methods on `BuilderInfo` all take a new argument `protoName`.
diff --git a/protobuf/lib/meta.dart b/protobuf/lib/meta.dart
index 920ac6e..66af3a0 100644
--- a/protobuf/lib/meta.dart
+++ b/protobuf/lib/meta.dart
@@ -43,6 +43,7 @@
   'mergeFromJson',
   'mergeFromJsonMap',
   'mergeFromMessage',
+  'mergeFromProto3Json',
   'mergeUnknownFields',
   'noSuchMethod',
   'runtimeType',
@@ -50,6 +51,7 @@
   'setField',
   'toBuilder',
   'toDebugString',
+  'toProto3Json',
   'toString',
   'unknownFields',
   'writeToBuffer',
diff --git a/protobuf/lib/protobuf.dart b/protobuf/lib/protobuf.dart
index 905e43d..4f2332f 100644
--- a/protobuf/lib/protobuf.dart
+++ b/protobuf/lib/protobuf.dart
@@ -13,6 +13,10 @@
 
 import 'package:fixnum/fixnum.dart' show Int64;
 
+import 'src/protobuf/json_parsing_context.dart';
+import 'src/protobuf/type_registry.dart';
+export 'src/protobuf/type_registry.dart' show TypeRegistry;
+
 part 'src/protobuf/coded_buffer.dart';
 part 'src/protobuf/coded_buffer_reader.dart';
 part 'src/protobuf/coded_buffer_writer.dart';
@@ -32,6 +36,7 @@
 part 'src/protobuf/pb_list.dart';
 part 'src/protobuf/pb_map.dart';
 part 'src/protobuf/protobuf_enum.dart';
+part 'src/protobuf/proto3_json.dart';
 part 'src/protobuf/readonly_message.dart';
 part 'src/protobuf/rpc_client.dart';
 part 'src/protobuf/unknown_field_set.dart';
diff --git a/protobuf/lib/src/protobuf/builder_info.dart b/protobuf/lib/src/protobuf/builder_info.dart
index 98bb04e..c779210 100644
--- a/protobuf/lib/src/protobuf/builder_info.dart
+++ b/protobuf/lib/src/protobuf/builder_info.dart
@@ -18,7 +18,18 @@
   bool hasRequiredFields = true;
   List<FieldInfo> _sortedByTag;
 
-  BuilderInfo(String messageName, {PackageName package = const PackageName('')})
+  // For well-known types.
+  final Object Function(GeneratedMessage message, TypeRegistry typeRegistry)
+      toProto3Json;
+  final Function(GeneratedMessage targetMessage, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) fromProto3Json;
+  final CreateBuilderFunc createEmptyInstance;
+
+  BuilderInfo(String messageName,
+      {PackageName package = const PackageName(''),
+      this.createEmptyInstance,
+      this.toProto3Json,
+      this.fromProto3Json})
       : qualifiedMessageName = "${package.prefix}$messageName";
 
   void add<T>(
@@ -42,12 +53,17 @@
     _addField(fieldInfo);
   }
 
-  void addMapField<K, V>(int tagNumber, String name, int keyFieldType,
-      int valueFieldType, BuilderInfo mapEntryBuilderInfo,
+  void addMapField<K, V>(
+      int tagNumber,
+      String name,
+      int keyFieldType,
+      int valueFieldType,
+      BuilderInfo mapEntryBuilderInfo,
+      CreateBuilderFunc valueCreator,
       {String protoName}) {
     var index = byIndex.length;
     _addField(MapFieldInfo<K, V>(name, tagNumber, index, PbFieldType.M,
-        keyFieldType, valueFieldType, mapEntryBuilderInfo,
+        keyFieldType, valueFieldType, mapEntryBuilderInfo, valueCreator,
         protoName: protoName));
   }
 
@@ -165,8 +181,8 @@
       ..add(PbMap._valueFieldNumber, 'value', valueFieldType, null,
           valueCreator, valueOf, enumValues);
 
-    addMapField<K, V>(
-        tagNumber, name, keyFieldType, valueFieldType, mapEntryBuilderInfo,
+    addMapField<K, V>(tagNumber, name, keyFieldType, valueFieldType,
+        mapEntryBuilderInfo, valueCreator,
         protoName: protoName);
   }
 
diff --git a/protobuf/lib/src/protobuf/field_info.dart b/protobuf/lib/src/protobuf/field_info.dart
index 65ec162..91fe8cd 100644
--- a/protobuf/lib/src/protobuf/field_info.dart
+++ b/protobuf/lib/src/protobuf/field_info.dart
@@ -8,6 +8,9 @@
 class FieldInfo<T> {
   FrozenPbList<T> _emptyList;
 
+  /// Name of this field as the `json_name` reported by protoc.
+  ///
+  /// This will typically be in camel case.
   final String name;
 
   /// The name of this field as written in the proto-definition.
@@ -188,15 +191,25 @@
 }
 
 class MapFieldInfo<K, V> extends FieldInfo<PbMap<K, V>> {
-  int keyFieldType;
-  int valueFieldType;
-  CreateBuilderFunc valueCreator;
+  final int keyFieldType;
+  final int valueFieldType;
 
-  // BuilderInfo used when creating a field set for a map field.
+  /// Creates a new empty instance of the value type.
+  ///
+  /// `null` if the value type is not a Message type.
+  final CreateBuilderFunc valueCreator;
+
   final BuilderInfo _mapEntryBuilderInfo;
 
-  MapFieldInfo(String name, int tagNumber, int index, int type,
-      this.keyFieldType, this.valueFieldType, this._mapEntryBuilderInfo,
+  MapFieldInfo(
+      String name,
+      int tagNumber,
+      int index,
+      int type,
+      this.keyFieldType,
+      this.valueFieldType,
+      this._mapEntryBuilderInfo,
+      this.valueCreator,
       {String protoName})
       : super(name, tagNumber, index, type,
             defaultOrMaker: () =>
@@ -208,6 +221,9 @@
     assert(!_isEnum(type) || valueOf != null);
   }
 
+  FieldInfo get valueFieldInfo =>
+      _mapEntryBuilderInfo.fieldInfo[PbMap._valueFieldNumber];
+
   Map<K, V> _ensureMapField(_FieldSet fs) {
     return fs._ensureMapField<K, V>(this);
   }
diff --git a/protobuf/lib/src/protobuf/field_set.dart b/protobuf/lib/src/protobuf/field_set.dart
index 97071c6..bce29d8 100644
--- a/protobuf/lib/src/protobuf/field_set.dart
+++ b/protobuf/lib/src/protobuf/field_set.dart
@@ -206,6 +206,7 @@
 
   Map<K, V> _getDefaultMap<K, V>(MapFieldInfo<K, V> fi) {
     assert(fi.isMapField);
+
     if (_isReadOnly)
       return PbMap<K, V>.unmodifiable(PbMap<K, V>(
           fi.keyFieldType, fi.valueFieldType, fi._mapEntryBuilderInfo));
diff --git a/protobuf/lib/src/protobuf/generated_message.dart b/protobuf/lib/src/protobuf/generated_message.dart
index 24d1d1f..fa1f0ea 100644
--- a/protobuf/lib/src/protobuf/generated_message.dart
+++ b/protobuf/lib/src/protobuf/generated_message.dart
@@ -199,10 +199,58 @@
   /// literals; values with a 64-bit integer datatype (regardless of their
   /// actual runtime value) are represented as strings. Enumerated values are
   /// represented as their integer value.
+  ///
+  /// For the proto3 JSON format use: [toProto3JSON].
   String writeToJson() => jsonEncode(writeToJsonMap());
 
+  /// Returns an Object representing Proto3 JSON serialization of [this].
+  ///
+  /// The key for each field is be the camel-cased name of the field.
+  ///
+  /// Well-known types and their special JSON encoding are supported.
+  /// If a well-known type cannot be encoded (eg. a `google.protobuf.Timestamp`
+  /// with negative `nanoseconds`) an error is thrown.
+  ///
+  /// Extensions and unknown fields are not encoded.
+  ///
+  /// The [typeRegistry] is be used for encoding `Any` messages. If an `Any`
+  /// message encoding a type not in [typeRegistry] is encountered, an
+  /// error is thrown.
+  Object toProto3Json(
+          {TypeRegistry typeRegistry = const TypeRegistry.empty()}) =>
+      _writeToProto3Json(_fieldSet, typeRegistry);
+
+  /// Merges field values from [json], a JSON object using proto3 encoding.
+  ///
+  /// Well-known types and their special JSON encoding are supported.
+  /// Except `FieldMask`.
+  ///
+  /// If [ignoreUnknownFields] is `false` (the default) an
+  /// [FormatException] is be thrown if an unknown field name
+  /// is encountered. Otherwise the unknown field is ignored.
+  ///
+  /// If [supportNamesWithUnderscores] is `true` (the default) field names in
+  /// the JSON can be represented as either camel-case JSON-names or names with
+  /// underscores.
+  /// If `false` only the JSON names are supported.
+  ///
+  /// The [typeRegistry] is be used for decoding `Any` messages. If an `Any`
+  /// message encoding a type not in [typeRegistry] is encountered, a
+  /// [FormatException] is thrown.
+  ///
+  /// If the JSON is otherwise not formatted correctly (a String where a
+  /// number was expected etc.) a [FormatException] is thrown.
+  void mergeFromProto3Json(Object json,
+          {TypeRegistry typeRegistry = const TypeRegistry.empty(),
+          bool ignoreUnknownFields = false,
+          bool supportNamesWithUnderscores = true}) =>
+      _mergeFromProto3Json(json, _fieldSet, typeRegistry, ignoreUnknownFields,
+          supportNamesWithUnderscores);
+
   /// Merges field values from [data], a JSON object, encoded as described by
   /// [GeneratedMessage.writeToJson].
+  ///
+  /// For the proto3 JSON format use: [mergeFromProto3JSON].
   void mergeFromJson(String data,
       [ExtensionRegistry extensionRegistry = ExtensionRegistry.EMPTY]) {
     /// Disable lazy creation of Dart objects for a dart2js speedup.
diff --git a/protobuf/lib/src/protobuf/json_parsing_context.dart b/protobuf/lib/src/protobuf/json_parsing_context.dart
new file mode 100644
index 0000000..ad4e139
--- /dev/null
+++ b/protobuf/lib/src/protobuf/json_parsing_context.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+class JsonParsingContext {
+  // A list of indices into maps and lists pointing to the current root.
+  final List<String> _path = <String>[];
+  final bool ignoreUnknownFields;
+  final bool supportNamesWithUnderscores;
+  JsonParsingContext(
+      this.ignoreUnknownFields, this.supportNamesWithUnderscores);
+
+  void addMapIndex(String index) {
+    _path.add(index);
+  }
+
+  void addListIndex(int index) {
+    _path.add(index.toString());
+  }
+
+  void popIndex() {
+    _path.removeLast();
+  }
+
+  /// Returns a FormatException indicating the indices to the current [path].
+  Exception parseException(String message, Object source) {
+    String formattedPath = _path.map((s) => '[\"$s\"]').join();
+    return FormatException(
+        'Protobuf JSON decoding failed at: root$formattedPath. $message',
+        source);
+  }
+}
diff --git a/protobuf/lib/src/protobuf/mixins/well_known.dart b/protobuf/lib/src/protobuf/mixins/well_known.dart
index 6f8c647..06fd629 100644
--- a/protobuf/lib/src/protobuf/mixins/well_known.dart
+++ b/protobuf/lib/src/protobuf/mixins/well_known.dart
@@ -1,8 +1,16 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:convert';
+
 import 'package:fixnum/fixnum.dart';
 
+import '../json_parsing_context.dart';
 import '../../../protobuf.dart';
+import '../type_registry.dart';
 
-abstract class AnyMixin {
+abstract class AnyMixin implements GeneratedMessage {
   String get typeUrl;
   set typeUrl(String value);
   List<int> get value;
@@ -40,9 +48,99 @@
     target.value = message.writeToBuffer();
     target.typeUrl = '${typeUrlPrefix}/${message.info_.qualifiedMessageName}';
   }
+
+  // From google/protobuf/any.proto:
+  // JSON
+  // ====
+  // The JSON representation of an `Any` value uses the regular
+  // representation of the deserialized, embedded message, with an
+  // additional field `@type` which contains the type URL. Example:
+  //
+  //     package google.profile;
+  //     message Person {
+  //       string first_name = 1;
+  //       string last_name = 2;
+  //     }
+  //
+  //     {
+  //       "@type": "type.googleapis.com/google.profile.Person",
+  //       "firstName": <string>,
+  //       "lastName": <string>
+  //     }
+  //
+  // If the embedded message type is well-known and has a custom JSON
+  // representation, that representation will be embedded adding a field
+  // `value` which holds the custom JSON in addition to the `@type`
+  // field. Example (for message [google.protobuf.Duration][]):
+  //
+  //     {
+  //       "@type": "type.googleapis.com/google.protobuf.Duration",
+  //       "value": "1.212s"
+  //     }
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    AnyMixin any = message as AnyMixin;
+    BuilderInfo info = typeRegistry.lookup(_typeNameFromUrl(any.typeUrl));
+    if (info == null)
+      throw ArgumentError(
+          'The type of the Any message (${any.typeUrl}) is not in the given typeRegistry.');
+    GeneratedMessage unpacked = info.createEmptyInstance()
+      ..mergeFromBuffer(any.value);
+    Object proto3Json = unpacked.toProto3Json();
+    if (info.toProto3Json == null) {
+      Map<String, dynamic> map = proto3Json as Map<String, dynamic>;
+      map['@type'] = any.typeUrl;
+      return map;
+    } else {
+      return {'@type': any.typeUrl, 'value': proto3Json};
+    }
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is! Map<String, dynamic>) {
+      throw context.parseException(
+          'Expected Any message encoded as {@type,...},', json);
+    }
+    final object = json as Map<String, dynamic>;
+    final typeUrl = object['@type'];
+
+    if (typeUrl is String) {
+      AnyMixin any = message as AnyMixin;
+      BuilderInfo info = typeRegistry.lookup(_typeNameFromUrl(typeUrl));
+      if (info == null) {
+        throw context.parseException(
+            'Decoding Any of type ${typeUrl} not in TypeRegistry $typeRegistry',
+            json);
+      }
+
+      Object subJson = info.fromProto3Json == null
+          // TODO(sigurdm): avoid cloning [object] here.
+          ? (Map<String, dynamic>.from(object)..remove('@type'))
+          : object['value'];
+      // TODO(sigurdm): We lose [context.path].
+      GeneratedMessage packedMessage = info.createEmptyInstance()
+        ..mergeFromProto3Json(subJson,
+            typeRegistry: typeRegistry,
+            supportNamesWithUnderscores: context.supportNamesWithUnderscores,
+            ignoreUnknownFields: context.ignoreUnknownFields);
+
+      any.value = packedMessage.writeToBuffer();
+      any.typeUrl = typeUrl;
+    } else {
+      throw context.parseException('Expected a string', json);
+    }
+  }
+}
+
+String _typeNameFromUrl(String typeUrl) {
+  int index = typeUrl.lastIndexOf('/');
+  return index < 0 ? '' : typeUrl.substring(index + 1);
 }
 
 abstract class TimestampMixin {
+  static final RegExp finalGroupsOfThreeZeroes = RegExp(r'(?:000)*$');
+
   Int64 get seconds;
   set seconds(Int64 value);
 
@@ -65,4 +163,575 @@
     target.seconds = Int64(micros ~/ Duration.microsecondsPerSecond);
     target.nanos = (micros % Duration.microsecondsPerSecond).toInt() * 1000;
   }
+
+  static String _twoDigits(int n) {
+    if (n >= 10) return "${n}";
+    return "0${n}";
+  }
+
+  static final DateTime _minTimestamp = DateTime.utc(1);
+  static final DateTime _maxTimestamp = DateTime.utc(9999, 13, 31, 23, 59, 59);
+
+  // From google/protobuf/timestamp.proto:
+  // # JSON Mapping
+  //
+  // In JSON format, the Timestamp type is encoded as a string in the
+  // [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+  // format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+  // where {year} is always expressed using four digits while {month}, {day},
+  // {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+  // seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+  // are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+  // is required. A proto3 JSON serializer should always use UTC (as indicated by
+  // "Z") when printing the Timestamp type and a proto3 JSON parser should be
+  // able to accept both UTC and other timezones (as indicated by an offset).
+  //
+  // For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+  // 01:30 UTC on January 15, 2017.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    TimestampMixin timestamp = message as TimestampMixin;
+    DateTime dateTime = timestamp.toDateTime();
+
+    if (timestamp.nanos < 0) {
+      throw ArgumentError(
+          'Timestamp with negative `nanos`: ${timestamp.nanos}');
+    }
+    if (timestamp.nanos > 999999999) {
+      throw ArgumentError(
+          'Timestamp with `nanos` out of range: ${timestamp.nanos}');
+    }
+    if (dateTime.isBefore(_minTimestamp) || dateTime.isAfter(_maxTimestamp)) {
+      throw ArgumentError('Timestamp Must be from 0001-01-01T00:00:00Z to '
+          '9999-12-31T23:59:59Z inclusive. Was: ${dateTime.toIso8601String()}');
+    }
+
+    // Because [DateTime] doesn't have nano-second precision, we cannot use
+    // dateTime.toIso8601String().
+    String y = '${dateTime.year}'.padLeft(4, '0');
+    String m = _twoDigits(dateTime.month);
+    String d = _twoDigits(dateTime.day);
+    String h = _twoDigits(dateTime.hour);
+    String min = _twoDigits(dateTime.minute);
+    String sec = _twoDigits(dateTime.second);
+    String secFrac = "";
+    if (timestamp.nanos > 0) {
+      secFrac = "." +
+          timestamp.nanos
+              .toString()
+              .padLeft(9, "0")
+              .replaceFirst(finalGroupsOfThreeZeroes, '');
+    }
+    return "$y-$m-${d}T$h:$min:$sec${secFrac}Z";
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is String) {
+      String jsonWithoutFracSec = json;
+      int nanos = 0;
+      Match fracSecsMatch = RegExp(r'\.(\d+)').firstMatch(json);
+      if (fracSecsMatch != null) {
+        String fracSecs = fracSecsMatch[1];
+        if (fracSecs.length > 9) {
+          throw context.parseException(
+              'Timestamp can have at most than 9 decimal digits', json);
+        }
+        nanos = int.parse(fracSecs.padRight(9, '0'));
+        jsonWithoutFracSec =
+            json.replaceRange(fracSecsMatch.start, fracSecsMatch.end, '');
+      }
+      DateTime dateTimeWithoutFractionalSeconds =
+          DateTime.tryParse(jsonWithoutFracSec) ??
+              (throw context.parseException(
+                  'Timestamp not well formatted. ', json));
+
+      TimestampMixin timestamp = message as TimestampMixin;
+      setFromDateTime(timestamp, dateTimeWithoutFractionalSeconds);
+      timestamp.nanos = nanos;
+    } else {
+      throw context.parseException(
+          'Expected timestamp represented as String', json);
+    }
+  }
+}
+
+abstract class DurationMixin {
+  Int64 get seconds;
+  set seconds(Int64 value);
+
+  int get nanos;
+  set nanos(int value);
+
+  static final RegExp finalZeroes = RegExp(r'0+$');
+
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    DurationMixin duration = message as DurationMixin;
+    String secFrac = duration.nanos
+        // nanos and seconds should always have the same sign.
+        .abs()
+        .toString()
+        .padLeft(9, '0')
+        .replaceFirst(finalZeroes, '');
+    String secPart = secFrac == '' ? '' : '.$secFrac';
+    return "${duration.seconds}${secPart}s";
+  }
+
+  static final RegExp durationPattern = RegExp(r'(-?\d*)(?:\.(\d*))?s$');
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    DurationMixin duration = message as DurationMixin;
+    if (json is String) {
+      Match match = durationPattern.matchAsPrefix(json);
+      if (match == null) {
+        throw context.parseException(
+            'Expected a String of the form `<seconds>.<nanos>s`', json);
+      } else {
+        String secondsString = match[1];
+        Int64 seconds =
+            secondsString == '' ? Int64.ZERO : Int64.parseInt(secondsString);
+        duration.seconds = seconds;
+        int nanos = int.parse((match[2] ?? '').padRight(9, '0'));
+        duration.nanos = seconds < 0 ? -nanos : nanos;
+      }
+    } else {
+      throw context.parseException(
+          'Expected a String of the form `<seconds>.<nanos>s`', json);
+    }
+  }
+}
+
+abstract class StructMixin implements GeneratedMessage {
+  Map<String, ValueMixin> get fields;
+  static const _fieldsFieldTagNumber = 1;
+
+  // From google/protobuf/struct.proto:
+  // The JSON representation for `Struct` is JSON object.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    StructMixin struct = message as StructMixin;
+    return struct.fields.map((key, value) =>
+        MapEntry(key, ValueMixin.toProto3JsonHelper(value, typeRegistry)));
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is Map) {
+      // Check for emptiness to avoid setting `.fields` if there are no
+      // values.
+      if (json.isNotEmpty) {
+        Map<String, ValueMixin> fields = (message as StructMixin).fields;
+        GeneratedMessage Function() valueCreator =
+            (message.info_.fieldInfo[_fieldsFieldTagNumber] as MapFieldInfo)
+                .valueCreator;
+
+        json.forEach((key, value) {
+          if (key is! String) {
+            throw context.parseException('Expected String key', json);
+          }
+          ValueMixin v = valueCreator();
+          context.addMapIndex(key);
+          ValueMixin.fromProto3JsonHelper(v, value, typeRegistry, context);
+          context.popIndex();
+          fields[key] = v;
+        });
+      }
+    } else {
+      throw context.parseException(
+          'Expected a JSON object literal (map)', json);
+    }
+  }
+}
+
+abstract class ValueMixin implements GeneratedMessage {
+  bool hasNullValue();
+  ProtobufEnum get nullValue;
+  set nullValue(covariant ProtobufEnum value);
+  bool hasNumberValue();
+  double get numberValue;
+  set numberValue(double v);
+  bool hasStringValue();
+  String get stringValue;
+  set stringValue(String v);
+  bool hasBoolValue();
+  bool get boolValue;
+  set boolValue(bool v);
+  bool hasStructValue();
+  StructMixin get structValue;
+  set structValue(covariant StructMixin v);
+  bool hasListValue();
+  ListValueMixin get listValue;
+  set listValue(covariant ListValueMixin v);
+
+  // From google/protobuf/struct.proto:
+  // The JSON representation for `Value` is JSON value
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    ValueMixin value = message as ValueMixin;
+    // This would ideally be a switch, but we cannot import the enum we are
+    // switching over.
+    if (value.hasNullValue()) {
+      return null;
+    } else if (value.hasNumberValue()) {
+      return value.numberValue;
+    } else if (value.hasStringValue()) {
+      return value.stringValue;
+    } else if (value.hasBoolValue()) {
+      return value.boolValue;
+    } else if (value.hasStructValue()) {
+      return StructMixin.toProto3JsonHelper(value.structValue, typeRegistry);
+    } else if (value.hasListValue()) {
+      return ListValueMixin.toProto3JsonHelper(value.listValue, typeRegistry);
+    } else {
+      throw ArgumentError('Serializing google.protobuf.Value with no value');
+    }
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    ValueMixin value = message as ValueMixin;
+    if (json == null) {
+      // Rely on the getter retrieving the default to provide an instance.
+      value.nullValue = value.nullValue;
+    } else if (json is num) {
+      value.numberValue = json.toDouble();
+    } else if (json is String) {
+      value.stringValue = json;
+    } else if (json is bool) {
+      value.boolValue = json;
+    } else if (json is Map) {
+      // Clone because the default instance is frozen.
+      StructMixin structValue = value.structValue.clone();
+      StructMixin.fromProto3JsonHelper(
+          structValue, json, typeRegistry, context);
+      value.structValue = structValue;
+    } else if (json is List) {
+      // Clone because the default instance is frozen.
+      ListValueMixin listValue = value.listValue.clone();
+      ListValueMixin.fromProto3JsonHelper(
+          listValue, json, typeRegistry, context);
+      value.listValue = listValue;
+    } else {
+      throw context.parseException(
+          'Expected a json-value (Map, List, String, number, bool or null)',
+          json);
+    }
+  }
+}
+
+abstract class ListValueMixin implements GeneratedMessage {
+  List<ValueMixin> get values;
+
+  // From google/protobuf/struct.proto:
+  // The JSON representation for `ListValue` is JSON array.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    ListValueMixin list = message as ListValueMixin;
+    return list.values
+        .map((value) => ValueMixin.toProto3JsonHelper(value, typeRegistry))
+        .toList();
+  }
+
+  static const _valueFieldTagNumber = 1;
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    ListValueMixin list = message as ListValueMixin;
+    if (json is List) {
+      CreateBuilderFunc subBuilder =
+          message.info_.subBuilder(_valueFieldTagNumber);
+      for (int i = 0; i < json.length; i++) {
+        Object element = json[i];
+        ValueMixin v = subBuilder();
+        context.addListIndex(i);
+        ValueMixin.fromProto3JsonHelper(v, element, typeRegistry, context);
+        context.popIndex();
+        list.values.add(v);
+      }
+    } else {
+      throw context.parseException('Expected a json-List', json);
+    }
+  }
+}
+
+abstract class FieldMaskMixin {
+  List<String> get paths;
+
+  // From google/protobuf/field_mask.proto:
+  // # JSON Encoding of Field Masks
+  //
+  // In JSON, a field mask is encoded as a single string where paths are
+  // separated by a comma. Fields name in each path are converted
+  // to/from lower-camel naming conventions.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    FieldMaskMixin fieldMask = message as FieldMaskMixin;
+    for (String path in fieldMask.paths) {
+      if (path.indexOf(RegExp('[A-Z]|_[^a-z]')) != -1) {
+        throw ArgumentError(
+            'Bad fieldmask $path. Does not round-trip to json.');
+      }
+    }
+    return fieldMask.paths.map(_toCamelCase).join(',');
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is String) {
+      if (json.indexOf('_') != -1) {
+        throw context.parseException(
+            'Invalid Character `_` in FieldMask', json);
+      }
+      if (json == '') {
+        // The empty string splits to a single value. So this is a special case.
+        return;
+      }
+      (message as FieldMaskMixin).paths
+        ..addAll(json.split(',').map(_fromCamelCase));
+    } else {
+      throw context.parseException(
+          'Expected String formatted as FieldMask', json);
+    }
+  }
+
+  static String _toCamelCase(String name) {
+    return name.replaceAllMapped(
+        RegExp('_([a-z])'), (Match m) => '${m.group(1).toUpperCase()}');
+  }
+
+  static String _fromCamelCase(String name) {
+    return name.replaceAllMapped(
+        RegExp('[A-Z]'), (Match m) => '_${m.group(0).toLowerCase()}');
+  }
+}
+
+abstract class DoubleValueMixin {
+  double get value;
+  set value(double value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `DoubleValue` is JSON number.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as DoubleValueMixin).value;
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is num) {
+      (message as DoubleValueMixin).value = json.toDouble();
+    } else if (json is String) {
+      (message as DoubleValueMixin).value = double.tryParse(json) ??
+          (throw context.parseException(
+              'Expected string to encode a double', json));
+    } else {
+      throw context.parseException(
+          'Expected a double as a String or number', json);
+    }
+  }
+}
+
+abstract class FloatValueMixin {
+  double get value;
+  set value(double value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `FloatValue` is JSON number.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as FloatValueMixin).value;
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is num) {
+      (message as FloatValueMixin).value = json.toDouble();
+    } else if (json is String) {
+      (message as FloatValueMixin).value = double.tryParse(json) ??
+          (throw context.parseException(
+              'Expected a float as a String or number', json));
+    } else {
+      throw context.parseException(
+          'Expected a float as a String or number', json);
+    }
+  }
+}
+
+abstract class Int64ValueMixin {
+  Int64 get value;
+  set value(Int64 value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `Int64Value` is JSON string.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as Int64ValueMixin).value.toString();
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is int) {
+      (message as Int64ValueMixin).value = Int64(json);
+    } else if (json is String) {
+      try {
+        (message as Int64ValueMixin).value = Int64.parseInt(json);
+      } on FormatException {
+        throw context.parseException('Expected string to encode integer', json);
+      }
+    } else {
+      throw context.parseException(
+          'Expected an integer encoded as a String or number', json);
+    }
+  }
+}
+
+abstract class UInt64ValueMixin {
+  Int64 get value;
+  set value(Int64 value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `UInt64Value` is JSON string.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as UInt64ValueMixin).value.toStringUnsigned();
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is int) {
+      (message as UInt64ValueMixin).value = Int64(json);
+    } else if (json is String) {
+      try {
+        (message as UInt64ValueMixin).value = Int64.parseInt(json);
+      } on FormatException {
+        throw context.parseException(
+            'Expected string to encode unsigned integer', json);
+      }
+    } else {
+      throw context.parseException(
+          'Expected an unsigned integer as a String or integer', json);
+    }
+  }
+}
+
+abstract class Int32ValueMixin {
+  int get value;
+  set value(int value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `Int32Value` is JSON number.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as Int32ValueMixin).value;
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is int) {
+      (message as Int32ValueMixin).value = json;
+    } else if (json is String) {
+      (message as Int32ValueMixin).value = int.tryParse(json) ??
+          (throw context.parseException(
+              'Expected string to encode integer', json));
+    } else {
+      throw context.parseException(
+          'Expected an integer encoded as a String or number', json);
+    }
+  }
+}
+
+abstract class UInt32ValueMixin {
+  int get value;
+  set value(int value);
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as UInt32ValueMixin).value;
+  }
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `UInt32Value` is JSON number.
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is int) {
+      (message as UInt32ValueMixin).value = json;
+    } else if (json is String) {
+      (message as UInt32ValueMixin).value = int.tryParse(json) ??
+          (throw context.parseException(
+              'Expected String to encode an integer', json));
+    } else {
+      throw context.parseException(
+          'Expected an unsigned integer as a String or integer', json);
+    }
+  }
+}
+
+abstract class BoolValueMixin {
+  bool get value;
+  set value(bool value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `BoolValue` is JSON `true` and `false`
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as BoolValueMixin).value;
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is bool) {
+      (message as BoolValueMixin).value = json;
+    } else {
+      throw context.parseException('Expected a bool', json);
+    }
+  }
+}
+
+abstract class StringValueMixin {
+  String get value;
+  set value(String value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `StringValue` is JSON string.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return (message as StringValueMixin).value;
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is String) {
+      (message as StringValueMixin).value = json;
+    } else {
+      throw context.parseException('Expected a String', json);
+    }
+  }
+}
+
+abstract class BytesValueMixin {
+  List<int> get value;
+  set value(List<int> value);
+
+  // From google/protobuf/wrappers.proto:
+  // The JSON representation for `BytesValue` is JSON string.
+  static Object toProto3JsonHelper(
+      GeneratedMessage message, TypeRegistry typeRegistry) {
+    return base64.encode((message as BytesValueMixin).value);
+  }
+
+  static void fromProto3JsonHelper(GeneratedMessage message, Object json,
+      TypeRegistry typeRegistry, JsonParsingContext context) {
+    if (json is String) {
+      try {
+        (message as BytesValueMixin).value = base64.decode(json);
+      } on FormatException {
+        throw context.parseException(
+            'Expected bytes encoded as base64 String', json);
+      }
+    } else {
+      throw context.parseException(
+          'Expected bytes encoded as base64 String', json);
+    }
+  }
 }
diff --git a/protobuf/lib/src/protobuf/proto3_json.dart b/protobuf/lib/src/protobuf/proto3_json.dart
new file mode 100644
index 0000000..d3bae84
--- /dev/null
+++ b/protobuf/lib/src/protobuf/proto3_json.dart
@@ -0,0 +1,402 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of protobuf;
+
+Object _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
+  String convertToMapKey(dynamic key, int keyType) {
+    int baseType = PbFieldType._baseType(keyType);
+
+    assert(!_isRepeated(keyType));
+
+    switch (baseType) {
+      case PbFieldType._BOOL_BIT:
+        return key ? 'true' : 'false';
+      case PbFieldType._STRING_BIT:
+        return key;
+      case PbFieldType._UINT64_BIT:
+        return (key as Int64).toStringUnsigned();
+      case PbFieldType._INT32_BIT:
+      case PbFieldType._SINT32_BIT:
+      case PbFieldType._UINT32_BIT:
+      case PbFieldType._FIXED32_BIT:
+      case PbFieldType._SFIXED32_BIT:
+      case PbFieldType._INT64_BIT:
+      case PbFieldType._SINT64_BIT:
+      case PbFieldType._SFIXED64_BIT:
+      case PbFieldType._FIXED64_BIT:
+        return key.toString();
+      default:
+        throw StateError('Not a valid key type $keyType');
+    }
+  }
+
+  Object valueToProto3Json(dynamic fieldValue, int fieldType) {
+    if (fieldValue == null) return null;
+
+    if (_isGroupOrMessage(fieldType)) {
+      return _writeToProto3Json(
+          (fieldValue as GeneratedMessage)._fieldSet, typeRegistry);
+    } else if (_isEnum(fieldType)) {
+      return (fieldValue as ProtobufEnum).name;
+    } else {
+      int baseType = PbFieldType._baseType(fieldType);
+      switch (baseType) {
+        case PbFieldType._BOOL_BIT:
+          return fieldValue ? true : false;
+        case PbFieldType._STRING_BIT:
+          return fieldValue;
+        case PbFieldType._INT32_BIT:
+        case PbFieldType._SINT32_BIT:
+        case PbFieldType._UINT32_BIT:
+        case PbFieldType._FIXED32_BIT:
+        case PbFieldType._SFIXED32_BIT:
+          return fieldValue;
+        case PbFieldType._INT64_BIT:
+        case PbFieldType._SINT64_BIT:
+        case PbFieldType._SFIXED64_BIT:
+        case PbFieldType._FIXED64_BIT:
+          return fieldValue.toString();
+        case PbFieldType._FLOAT_BIT:
+        case PbFieldType._DOUBLE_BIT:
+          double value = fieldValue;
+          if (value.isNaN) return 'NaN';
+          if (value.isInfinite) {
+            if (value.isNegative) {
+              return '-Infinity';
+            } else {
+              return 'Infinity';
+            }
+          }
+          return value;
+        case PbFieldType._UINT64_BIT:
+          return (fieldValue as Int64).toStringUnsigned();
+        case PbFieldType._BYTES_BIT:
+          return base64Encode(fieldValue);
+        default:
+          throw StateError(
+              'Invariant violation: unexpected value type $fieldType');
+      }
+    }
+  }
+
+  if (fs._meta.toProto3Json != null) {
+    return fs._meta.toProto3Json(fs._message, typeRegistry);
+  }
+
+  Map<String, dynamic> result = <String, dynamic>{};
+  for (FieldInfo fieldInfo in fs._infosSortedByTag) {
+    var value = fs._values[fieldInfo.index];
+    if (value == null || (value is List && value.isEmpty)) {
+      continue; // It's missing, repeated, or an empty byte array.
+    }
+    dynamic jsonValue;
+    if (fieldInfo.isMapField) {
+      jsonValue = (value as PbMap).map((key, entryValue) {
+        MapFieldInfo mapEntryInfo = fieldInfo as MapFieldInfo;
+        return MapEntry(convertToMapKey(key, mapEntryInfo.keyFieldType),
+            valueToProto3Json(entryValue, mapEntryInfo.valueFieldType));
+      });
+    } else if (fieldInfo.isRepeated) {
+      jsonValue = (value as PbList)
+          .map((element) => valueToProto3Json(element, fieldInfo.type))
+          .toList();
+    } else {
+      jsonValue = valueToProto3Json(value, fieldInfo.type);
+    }
+    result[fieldInfo.name] = jsonValue;
+  }
+  // Extensions and unknown fields are not encoded by proto3 JSON.
+  return result;
+}
+
+void _mergeFromProto3Json(
+    Object json,
+    _FieldSet fieldSet,
+    TypeRegistry typeRegistry,
+    bool ignoreUnknownFields,
+    bool supportNamesWithUnderscores) {
+  JsonParsingContext context =
+      JsonParsingContext(ignoreUnknownFields, supportNamesWithUnderscores);
+
+  void recursionHelper(Object json, _FieldSet fieldSet) {
+    int tryParse32Bit(String s) {
+      return int.tryParse(s) ??
+          (throw context.parseException('expected integer', s));
+    }
+
+    int check32BitSigned(int n) {
+      if (n < -2147483648 || n > 2147483647) {
+        throw context.parseException('expected 32 bit unsigned integer', n);
+      }
+      return n;
+    }
+
+    int check32BitUnsigned(int n) {
+      if (n < 0 || n > 0xFFFFFFFF) {
+        throw context.parseException('expected 32 bit unsigned integer', n);
+      }
+      return n;
+    }
+
+    Int64 tryParse64Bit(String s) {
+      Int64 result;
+      try {
+        result = Int64.parseInt(s);
+      } on FormatException {
+        throw context.parseException('expected integer', json);
+      }
+      return result;
+    }
+
+    Object convertProto3JsonValue(Object value, FieldInfo fieldInfo) {
+      if (value == null) {
+        return fieldInfo.makeDefault();
+      }
+      int fieldType = fieldInfo.type;
+      switch (PbFieldType._baseType(fieldType)) {
+        case PbFieldType._BOOL_BIT:
+          if (value is bool) {
+            return value;
+          }
+          throw context.parseException('Expected bool value', json);
+        case PbFieldType._BYTES_BIT:
+          if (value is String) {
+            Uint8List result;
+            try {
+              result = base64Decode(value);
+            } on FormatException {
+              throw context.parseException(
+                  'Expected bytes encoded as base64 String', json);
+            }
+            return result;
+          }
+          throw context.parseException(
+              'Expected bytes encoded as base64 String', value);
+        case PbFieldType._STRING_BIT:
+          if (value is String) {
+            return value;
+          }
+          throw context.parseException('Expected String value', value);
+        case PbFieldType._FLOAT_BIT:
+        case PbFieldType._DOUBLE_BIT:
+          if (value is double) {
+            return value;
+          } else if (value is num) {
+            return value.toDouble();
+          } else if (value is String) {
+            return double.tryParse(value) ??
+                (throw context.parseException(
+                    'Expected String to encode a double', value));
+          }
+          throw context.parseException(
+              'Expected a double represented as a String or number', value);
+        case PbFieldType._ENUM_BIT:
+          if (value is String) {
+            // TODO(sigurdm): Do we want to avoid linear search here? Measure...
+            return fieldInfo.enumValues.firstWhere((e) => e.name == value,
+                orElse: () =>
+                    throw context.parseException('Unknown enum value', value));
+          } else if (value is int) {
+            return fieldInfo.valueOf(value) ??
+                (throw context.parseException('Unknown enum value', value));
+          }
+          throw context.parseException(
+              'Expected enum as a string or integer', value);
+        case PbFieldType._UINT32_BIT:
+          int result;
+          if (value is int) {
+            result = value;
+          } else if (value is String) {
+            result = tryParse32Bit(value);
+          } else {
+            throw context.parseException(
+                'Expected int or stringified int', value);
+          }
+          return check32BitUnsigned(result);
+        case PbFieldType._INT32_BIT:
+        case PbFieldType._SINT32_BIT:
+        case PbFieldType._UINT32_BIT:
+        case PbFieldType._FIXED32_BIT:
+        case PbFieldType._SFIXED32_BIT:
+          int result;
+          if (value is int) {
+            result = value;
+          } else if (value is String) {
+            result = tryParse32Bit(value);
+          } else {
+            throw context.parseException(
+                'Expected int or stringified int', value);
+          }
+          check32BitSigned(result);
+          return result;
+        case PbFieldType._UINT64_BIT:
+          Int64 result;
+          if (value is int) {
+            result = Int64(value);
+          } else if (value is String) {
+            result = tryParse64Bit(value);
+          } else {
+            throw context.parseException(
+                'Expected int or stringified int', value);
+          }
+          return result;
+        case PbFieldType._INT64_BIT:
+        case PbFieldType._SINT64_BIT:
+        case PbFieldType._FIXED64_BIT:
+        case PbFieldType._SFIXED64_BIT:
+          if (value is int) return Int64(value);
+          if (value is String) {
+            Int64 result;
+            try {
+              result = Int64.parseInt(value);
+            } on FormatException {
+              throw context.parseException(
+                  'Expected int or stringified int', value);
+            }
+            return result;
+          }
+          throw context.parseException(
+              'Expected int or stringified int', value);
+        case PbFieldType._GROUP_BIT:
+        case PbFieldType._MESSAGE_BIT:
+          GeneratedMessage subMessage = fieldInfo.subBuilder();
+          recursionHelper(value, subMessage._fieldSet);
+          return subMessage;
+        default:
+          throw StateError('Unknown type $fieldType');
+      }
+    }
+
+    Object decodeMapKey(String key, int fieldType) {
+      switch (PbFieldType._baseType(fieldType)) {
+        case PbFieldType._BOOL_BIT:
+          switch (key) {
+            case 'true':
+              return true;
+            case 'false':
+              return false;
+            default:
+              throw context.parseException(
+                  'Wrong boolean key, should be one of ("true", "false")', key);
+          }
+          // ignore: dead_code
+          throw StateError('(Should have been) unreachable statement');
+        case PbFieldType._STRING_BIT:
+          return key;
+        case PbFieldType._UINT64_BIT:
+          // TODO(sigurdm): We do not throw on negative values here.
+          // That would probably require going via bignum.
+          return tryParse64Bit(key);
+        case PbFieldType._INT64_BIT:
+        case PbFieldType._SINT64_BIT:
+        case PbFieldType._SFIXED64_BIT:
+        case PbFieldType._FIXED64_BIT:
+          return tryParse64Bit(key);
+        case PbFieldType._INT32_BIT:
+        case PbFieldType._SINT32_BIT:
+        case PbFieldType._FIXED32_BIT:
+        case PbFieldType._SFIXED32_BIT:
+          return check32BitSigned(tryParse32Bit(key));
+        case PbFieldType._UINT32_BIT:
+          return check32BitUnsigned(tryParse32Bit(key));
+        default:
+          throw StateError('Not a valid key type $fieldType');
+      }
+    }
+
+    if (json == null) {
+      // `null` represents the default value. Do nothing more.
+      return;
+    }
+
+    BuilderInfo info = fieldSet._meta;
+
+    final wellKnownConverter = info.fromProto3Json;
+    if (wellKnownConverter != null) {
+      wellKnownConverter(fieldSet._message, json, typeRegistry, context);
+    } else {
+      if (json is Map) {
+        Map<String, FieldInfo> byName = info.byName;
+
+        json.forEach((key, value) {
+          if (key is! String) {
+            throw context.parseException('Key was not a String', key);
+          }
+          context.addMapIndex(key);
+
+          FieldInfo fieldInfo = byName[key];
+          if (fieldInfo == null && supportNamesWithUnderscores) {
+            // We don't optimize for field names with underscores, instead do a
+            // linear search for the index.
+            fieldInfo = byName.values.firstWhere(
+                (FieldInfo info) => info.protoName == key,
+                orElse: () => null);
+          }
+          if (fieldInfo == null) {
+            if (ignoreUnknownFields) {
+              return;
+            } else {
+              throw context.parseException('Unknown field name \'$key\'', key);
+            }
+          }
+
+          if (_isMapField(fieldInfo.type)) {
+            if (value is Map) {
+              MapFieldInfo mapFieldInfo = fieldInfo;
+              Map fieldValues = fieldSet._ensureMapField(fieldInfo);
+              value.forEach((subKey, subValue) {
+                if (subKey is! String) {
+                  throw context.parseException('Expected a String key', subKey);
+                }
+                context.addMapIndex(subKey);
+                final result = fieldValues[
+                        decodeMapKey(subKey, mapFieldInfo.keyFieldType)] =
+                    convertProto3JsonValue(
+                        subValue, mapFieldInfo.valueFieldInfo);
+                context.popIndex();
+                return result;
+              });
+            } else {
+              throw context.parseException('Expected a map', value);
+            }
+          } else if (_isRepeated(fieldInfo.type)) {
+            if (value == null) {
+              // `null` is accepted as the empty list [].
+              fieldSet._ensureRepeatedField(fieldInfo);
+            } else if (value is List) {
+              List values = fieldSet._ensureRepeatedField(fieldInfo);
+              for (int i = 0; i < value.length; i++) {
+                final entry = value[i];
+                context.addListIndex(i);
+                values.add(convertProto3JsonValue(entry, fieldInfo));
+                context.popIndex();
+              }
+            } else {
+              throw context.parseException('Expected a list', value);
+            }
+          } else if (_isGroupOrMessage(fieldInfo.type)) {
+            // TODO(sigurdm) consider a cleaner separation between parsing and merging.
+            GeneratedMessage parsedSubMessage =
+                convertProto3JsonValue(value, fieldInfo);
+            GeneratedMessage original = fieldSet._values[fieldInfo.index];
+            if (original == null) {
+              fieldSet._values[fieldInfo.index] = parsedSubMessage;
+            } else {
+              original.mergeFromMessage(parsedSubMessage);
+            }
+          } else {
+            fieldSet._setFieldUnchecked(
+                fieldInfo, convertProto3JsonValue(value, fieldInfo));
+          }
+          context.popIndex();
+        });
+      } else {
+        throw context.parseException('Expected JSON object', json);
+      }
+    }
+  }
+
+  recursionHelper(json, fieldSet);
+}
diff --git a/protobuf/lib/src/protobuf/type_registry.dart b/protobuf/lib/src/protobuf/type_registry.dart
new file mode 100644
index 0000000..8d73952
--- /dev/null
+++ b/protobuf/lib/src/protobuf/type_registry.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import '../../protobuf.dart';
+
+/// A TypeRegistry is used to resolve Any messages in the proto3 JSON conversion.
+///
+/// You must provide a TypeRegistry containing all message types used in
+/// Any message fields, or the JSON conversion will fail because data
+/// in Any message fields is unrecognizable. You don't need to supply a
+/// TypeRegistry if you don't use Any message fields.
+class TypeRegistry {
+  final Map<String, BuilderInfo> _mapping;
+
+  /// Constructs a new TypeRegistry recognizing the given types of messages.
+  ///
+  /// You can use an empty message of the given type to represent the type. Eg:
+  ///
+  /// ```dart
+  /// TypeRegistry([Foo(), Bar()]);
+  /// ```
+  TypeRegistry(Iterable<GeneratedMessage> types)
+      : _mapping = Map.fromEntries(types.map((message) =>
+            MapEntry(message.info_.qualifiedMessageName, message.info_)));
+
+  const TypeRegistry.empty() : _mapping = const {};
+
+  BuilderInfo lookup(String qualifiedName) {
+    return _mapping[qualifiedName];
+  }
+}
diff --git a/protoc_plugin/Makefile b/protoc_plugin/Makefile
index 5c42682..5338686 100644
--- a/protoc_plugin/Makefile
+++ b/protoc_plugin/Makefile
@@ -12,10 +12,19 @@
 TEST_PROTO_LIST = \
 	_leading_underscores \
 	google/protobuf/any \
+	google/protobuf/api \
+	google/protobuf/duration \
+	google/protobuf/empty \
+	google/protobuf/field_mask \
+	google/protobuf/source_context \
+	google/protobuf/struct \
 	google/protobuf/timestamp \
+	google/protobuf/type \
 	google/protobuf/unittest_import \
 	google/protobuf/unittest_optimize_for \
+	google/protobuf/unittest_well_known_types \
 	google/protobuf/unittest \
+	google/protobuf/wrappers \
 	dart_name \
 	enum_extension \
 	extend_unittest \
diff --git a/protoc_plugin/lib/message_generator.dart b/protoc_plugin/lib/message_generator.dart
index e09566b..1c76f4f 100644
--- a/protoc_plugin/lib/message_generator.dart
+++ b/protoc_plugin/lib/message_generator.dart
@@ -291,6 +291,10 @@
     String packageClause = package == ''
         ? ''
         : ', package: const $_protobufImportPrefix.PackageName(\'$package\')';
+    String proto3JsonClause = (mixin?.hasProto3JsonHelpers ?? false)
+        ? ', toProto3Json: $_mixinImportPrefix.${mixin.name}.toProto3JsonHelper, '
+            'fromProto3Json: $_mixinImportPrefix.${mixin.name}.fromProto3JsonHelper'
+        : '';
     out.addAnnotatedBlock(
         'class ${classname} extends $_protobufImportPrefix.GeneratedMessage${mixinClause} {',
         '}', [
@@ -312,7 +316,9 @@
       }
       out.addBlock(
           'static final $_protobufImportPrefix.BuilderInfo _i = '
-              '$_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause)',
+              '$_protobufImportPrefix.BuilderInfo(\'${messageName}\'$packageClause'
+              ', createEmptyInstance: create'
+              '$proto3JsonClause)',
           ';', () {
         for (int oneof = 0; oneof < _oneofFields.length; oneof++) {
           List<int> tags =
diff --git a/protoc_plugin/lib/mixins.dart b/protoc_plugin/lib/mixins.dart
index 06b9dc6..151e351 100644
--- a/protoc_plugin/lib/mixins.dart
+++ b/protoc_plugin/lib/mixins.dart
@@ -34,13 +34,23 @@
   /// May be null if the mixin doesn't reserve any new names.
   final List<String> reservedNames;
 
-  ///  Code to inject into the class using the mixin.
+  /// Code to inject into the class using the mixin.
   ///
   /// Typically used for static helpers since you cannot mix in static members.
   final List<String> injectedHelpers;
 
-  const PbMixin(this.name,
-      {this.importFrom, this.parent, this.reservedNames, this.injectedHelpers});
+  /// If `True` the mixin should have static methods for converting to and from
+  /// proto3 Json.
+  final bool hasProto3JsonHelpers;
+
+  const PbMixin(
+    this.name, {
+    this.importFrom,
+    this.parent,
+    this.reservedNames,
+    this.injectedHelpers,
+    this.hasProto3JsonHelpers = false,
+  });
 
   /// Returns the mixin and its ancestors, in the order they should be applied.
   Iterable<PbMixin> findMixinsToApply() {
diff --git a/protoc_plugin/lib/well_known_types.dart b/protoc_plugin/lib/well_known_types.dart
index 9ac1829..787f6b5 100644
--- a/protoc_plugin/lib/well_known_types.dart
+++ b/protoc_plugin/lib/well_known_types.dart
@@ -7,9 +7,12 @@
 PbMixin wellKnownMixinForFullName(String qualifiedName) =>
     _wellKnownMixins[qualifiedName];
 
+const _wellKnownImportPath =
+    'package:protobuf/src/protobuf/mixins/well_known.dart';
+
 const _wellKnownMixins = {
   'google.protobuf.Any': PbMixin('AnyMixin',
-      importFrom: 'package:protobuf/src/protobuf/mixins/well_known.dart',
+      importFrom: _wellKnownImportPath,
       injectedHelpers: [
         '''
 /// Creates a new [Any] encoding [message].
@@ -23,9 +26,10 @@
       typeUrlPrefix: typeUrlPrefix);
   return result;
 }'''
-      ]),
+      ],
+      hasProto3JsonHelpers: true),
   'google.protobuf.Timestamp': PbMixin('TimestampMixin',
-      importFrom: 'package:protobuf/src/protobuf/mixins/well_known.dart',
+      importFrom: _wellKnownImportPath,
       injectedHelpers: [
         '''
 /// Creates a new instance from [dateTime].
@@ -36,5 +40,76 @@
   $_mixinImportPrefix.TimestampMixin.setFromDateTime(result, dateTime);
   return result;
 }'''
-      ]),
+      ],
+      hasProto3JsonHelpers: true),
+  'google.protobuf.Duration': PbMixin(
+    'DurationMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.Struct': PbMixin(
+    'StructMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.Value': PbMixin(
+    'ValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.ListValue': PbMixin(
+    'ListValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.DoubleValue': PbMixin(
+    'DoubleValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.FloatValue': PbMixin(
+    'FloatValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.Int64Value': PbMixin(
+    'Int64ValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.UInt64Value': PbMixin(
+    'UInt64ValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.Int32Value': PbMixin(
+    'Int32ValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.UInt32Value': PbMixin(
+    'UInt32ValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.BoolValue': PbMixin(
+    'BoolValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.StringValue': PbMixin(
+    'StringValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.BytesValue': PbMixin(
+    'BytesValueMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  ),
+  'google.protobuf.FieldMask': PbMixin(
+    'FieldMaskMixin',
+    importFrom: _wellKnownImportPath,
+    hasProto3JsonHelpers: true,
+  )
 };
diff --git a/protoc_plugin/test/goldens/grpc_service.pb b/protoc_plugin/test/goldens/grpc_service.pb
index 9954f5a..d46c0b1 100644
--- a/protoc_plugin/test/goldens/grpc_service.pb
+++ b/protoc_plugin/test/goldens/grpc_service.pb
@@ -10,7 +10,7 @@
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class Empty extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty')
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty', createEmptyInstance: create)
     ..hasRequiredFields = false
   ;
 
diff --git a/protoc_plugin/test/goldens/imports.pb b/protoc_plugin/test/goldens/imports.pb
index 27b8265..98fd25c 100644
--- a/protoc_plugin/test/goldens/imports.pb
+++ b/protoc_plugin/test/goldens/imports.pb
@@ -13,7 +13,7 @@
 import 'package2.pb.dart' as $2;
 
 class M extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo('M')
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('M', createEmptyInstance: create)
     ..a<M>(1, 'm', $pb.PbFieldType.OM, defaultOrMaker: M.getDefault, subBuilder: M.create)
     ..a<$1.M>(2, 'm1', $pb.PbFieldType.OM, defaultOrMaker: $1.M.getDefault, subBuilder: $1.M.create)
     ..a<$2.M>(3, 'm2', $pb.PbFieldType.OM, defaultOrMaker: $2.M.getDefault, subBuilder: $2.M.create)
diff --git a/protoc_plugin/test/goldens/messageGenerator b/protoc_plugin/test/goldens/messageGenerator
index 7a80061..abc7f36 100644
--- a/protoc_plugin/test/goldens/messageGenerator
+++ b/protoc_plugin/test/goldens/messageGenerator
@@ -1,5 +1,5 @@
 class PhoneNumber extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber')
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber', createEmptyInstance: create)
     ..aQS(1, 'number')
     ..e<PhoneNumber_PhoneType>(2, 'type', $pb.PbFieldType.OE, defaultOrMaker: PhoneNumber_PhoneType.MOBILE, valueOf: PhoneNumber_PhoneType.valueOf, enumValues: PhoneNumber_PhoneType.values)
     ..a<$core.String>(3, 'name', $pb.PbFieldType.OS, defaultOrMaker: '\$')
diff --git a/protoc_plugin/test/goldens/messageGenerator.meta b/protoc_plugin/test/goldens/messageGenerator.meta
index 3590367..03d3aaa 100644
--- a/protoc_plugin/test/goldens/messageGenerator.meta
+++ b/protoc_plugin/test/goldens/messageGenerator.meta
@@ -9,8 +9,8 @@
   path: 4
   path: 0
   sourceFile: 
-  begin: 443
-  end: 454
+  begin: 472
+  end: 483
 }
 annotation: {
   path: 4
@@ -18,8 +18,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 1371
-  end: 1377
+  begin: 1400
+  end: 1406
 }
 annotation: {
   path: 4
@@ -27,8 +27,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 1402
-  end: 1408
+  begin: 1431
+  end: 1437
 }
 annotation: {
   path: 4
@@ -36,8 +36,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 1461
-  end: 1470
+  begin: 1490
+  end: 1499
 }
 annotation: {
   path: 4
@@ -45,8 +45,8 @@
   path: 2
   path: 1
   sourceFile: 
-  begin: 1493
-  end: 1504
+  begin: 1522
+  end: 1533
 }
 annotation: {
   path: 4
@@ -54,8 +54,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 1554
-  end: 1558
+  begin: 1583
+  end: 1587
 }
 annotation: {
   path: 4
@@ -63,8 +63,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 1579
-  end: 1583
+  begin: 1608
+  end: 1612
 }
 annotation: {
   path: 4
@@ -72,8 +72,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 1642
-  end: 1649
+  begin: 1671
+  end: 1678
 }
 annotation: {
   path: 4
@@ -81,8 +81,8 @@
   path: 2
   path: 0
   sourceFile: 
-  begin: 1672
-  end: 1681
+  begin: 1701
+  end: 1710
 }
 annotation: {
   path: 4
@@ -90,8 +90,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 1722
-  end: 1726
+  begin: 1751
+  end: 1755
 }
 annotation: {
   path: 4
@@ -99,8 +99,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 1753
-  end: 1757
+  begin: 1782
+  end: 1786
 }
 annotation: {
   path: 4
@@ -108,8 +108,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 1810
-  end: 1817
+  begin: 1839
+  end: 1846
 }
 annotation: {
   path: 4
@@ -117,8 +117,8 @@
   path: 2
   path: 2
   sourceFile: 
-  begin: 1840
-  end: 1849
+  begin: 1869
+  end: 1878
 }
 annotation: {
   path: 4
@@ -126,8 +126,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 1939
-  end: 1954
+  begin: 1968
+  end: 1983
 }
 annotation: {
   path: 4
@@ -135,8 +135,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 2028
-  end: 2043
+  begin: 2057
+  end: 2072
 }
 annotation: {
   path: 4
@@ -144,8 +144,8 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 2145
-  end: 2163
+  begin: 2174
+  end: 2192
 }
 annotation: {
   path: 4
@@ -153,6 +153,6 @@
   path: 2
   path: 3
   sourceFile: 
-  begin: 2235
-  end: 2255
+  begin: 2264
+  end: 2284
 }
diff --git a/protoc_plugin/test/goldens/oneMessage.pb b/protoc_plugin/test/goldens/oneMessage.pb
index fe417ad..9886c38 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb
+++ b/protoc_plugin/test/goldens/oneMessage.pb
@@ -10,7 +10,7 @@
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class PhoneNumber extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber')
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('PhoneNumber', createEmptyInstance: create)
     ..aQS(1, 'number')
     ..a<$core.int>(2, 'type', $pb.PbFieldType.O3)
     ..a<$core.String>(3, 'name', $pb.PbFieldType.OS, defaultOrMaker: '\$')
diff --git a/protoc_plugin/test/goldens/oneMessage.pb.meta b/protoc_plugin/test/goldens/oneMessage.pb.meta
index daba01c..4e80e02 100644
--- a/protoc_plugin/test/goldens/oneMessage.pb.meta
+++ b/protoc_plugin/test/goldens/oneMessage.pb.meta
@@ -9,8 +9,8 @@
   path: 4
   path: 0
   sourceFile: test
-  begin: 564
-  end: 575
+  begin: 593
+  end: 604
 }
 annotation: {
   path: 4
@@ -18,8 +18,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 1492
-  end: 1498
+  begin: 1521
+  end: 1527
 }
 annotation: {
   path: 4
@@ -27,8 +27,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 1523
-  end: 1529
+  begin: 1552
+  end: 1558
 }
 annotation: {
   path: 4
@@ -36,8 +36,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 1582
-  end: 1591
+  begin: 1611
+  end: 1620
 }
 annotation: {
   path: 4
@@ -45,8 +45,8 @@
   path: 2
   path: 0
   sourceFile: test
-  begin: 1614
-  end: 1625
+  begin: 1643
+  end: 1654
 }
 annotation: {
   path: 4
@@ -54,8 +54,8 @@
   path: 2
   path: 1
   sourceFile: test
-  begin: 1663
-  end: 1667
+  begin: 1692
+  end: 1696
 }
 annotation: {
   path: 4
@@ -63,8 +63,8 @@
   path: 2
   path: 1
   sourceFile: test
-  begin: 1690
-  end: 1694
+  begin: 1719
+  end: 1723
 }
 annotation: {
   path: 4
@@ -72,8 +72,8 @@
   path: 2
   path: 1
   sourceFile: test
-  begin: 1749
-  end: 1756
+  begin: 1778
+  end: 1785
 }
 annotation: {
   path: 4
@@ -81,8 +81,8 @@
   path: 2
   path: 1
   sourceFile: test
-  begin: 1779
-  end: 1788
+  begin: 1808
+  end: 1817
 }
 annotation: {
   path: 4
@@ -90,8 +90,8 @@
   path: 2
   path: 2
   sourceFile: test
-  begin: 1829
-  end: 1833
+  begin: 1858
+  end: 1862
 }
 annotation: {
   path: 4
@@ -99,8 +99,8 @@
   path: 2
   path: 2
   sourceFile: test
-  begin: 1860
-  end: 1864
+  begin: 1889
+  end: 1893
 }
 annotation: {
   path: 4
@@ -108,8 +108,8 @@
   path: 2
   path: 2
   sourceFile: test
-  begin: 1917
-  end: 1924
+  begin: 1946
+  end: 1953
 }
 annotation: {
   path: 4
@@ -117,6 +117,6 @@
   path: 2
   path: 2
   sourceFile: test
-  begin: 1947
-  end: 1956
+  begin: 1976
+  end: 1985
 }
diff --git a/protoc_plugin/test/goldens/service.pb b/protoc_plugin/test/goldens/service.pb
index e719f50..fdaabe3 100644
--- a/protoc_plugin/test/goldens/service.pb
+++ b/protoc_plugin/test/goldens/service.pb
@@ -11,7 +11,7 @@
 import 'package:protobuf/protobuf.dart' as $pb;
 
 class Empty extends $pb.GeneratedMessage {
-  static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty')
+  static final $pb.BuilderInfo _i = $pb.BuilderInfo('Empty', createEmptyInstance: create)
     ..hasRequiredFields = false
   ;
 
diff --git a/protoc_plugin/test/proto3_json_test.dart b/protoc_plugin/test/proto3_json_test.dart
new file mode 100644
index 0000000..d9c1fa8
--- /dev/null
+++ b/protoc_plugin/test/proto3_json_test.dart
@@ -0,0 +1,981 @@
+#!/usr/bin/env dart
+// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:core' hide Duration;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+import 'package:test/test.dart';
+
+import '../out/protos/google/protobuf/any.pb.dart';
+import '../out/protos/google/protobuf/duration.pb.dart';
+import '../out/protos/google/protobuf/empty.pb.dart';
+import '../out/protos/google/protobuf/field_mask.pb.dart';
+import '../out/protos/google/protobuf/struct.pb.dart';
+import '../out/protos/google/protobuf/timestamp.pb.dart';
+import '../out/protos/google/protobuf/unittest.pb.dart';
+
+import '../out/protos/google/protobuf/unittest_well_known_types.pb.dart';
+import '../out/protos/google/protobuf/wrappers.pb.dart';
+import '../out/protos/map_field.pb.dart';
+import 'test_util.dart';
+
+final testAllTypesJson = {
+  'optionalInt32': 101,
+  'optionalInt64': '102',
+  'optionalUint32': 103,
+  'optionalUint64': '104',
+  'optionalSint32': 105,
+  'optionalSint64': '106',
+  'optionalFixed32': 107,
+  'optionalFixed64': '108',
+  'optionalSfixed32': 109,
+  'optionalSfixed64': '110',
+  'optionalFloat': 111.0,
+  'optionalDouble': 112.0,
+  'optionalBool': true,
+  'optionalString': '115',
+  'optionalBytes': 'MTE2',
+  'optionalgroup': {'a': 117},
+  'optionalNestedMessage': {'bb': 118},
+  'optionalForeignMessage': {'c': 119},
+  'optionalImportMessage': {'d': 120},
+  'optionalNestedEnum': 'BAZ',
+  'optionalForeignEnum': 'FOREIGN_BAZ',
+  'optionalImportEnum': 'IMPORT_BAZ',
+  'optionalStringPiece': '124',
+  'optionalCord': '125',
+  'repeatedInt32': [201, 301],
+  'repeatedInt64': ['202', '302'],
+  'repeatedUint32': [203, 303],
+  'repeatedUint64': ['204', '304'],
+  'repeatedSint32': [205, 305],
+  'repeatedSint64': ['206', '306'],
+  'repeatedFixed32': [207, 307],
+  'repeatedFixed64': ['208', '308'],
+  'repeatedSfixed32': [209, 309],
+  'repeatedSfixed64': ['210', '310'],
+  'repeatedFloat': [211.0, 311.0],
+  'repeatedDouble': [212.0, 312.0],
+  'repeatedBool': [true, false],
+  'repeatedString': ['215', '315'],
+  'repeatedBytes': ['MjE2', 'MzE2'],
+  'repeatedgroup': [
+    {'a': 217},
+    {'a': 317}
+  ],
+  'repeatedNestedMessage': [
+    {'bb': 218},
+    {'bb': 318}
+  ],
+  'repeatedForeignMessage': [
+    {'c': 219},
+    {'c': 319}
+  ],
+  'repeatedImportMessage': [
+    {'d': 220},
+    {'d': 320}
+  ],
+  'repeatedNestedEnum': ['BAR', 'BAZ'],
+  'repeatedForeignEnum': ['FOREIGN_BAR', 'FOREIGN_BAZ'],
+  'repeatedImportEnum': ['IMPORT_BAR', 'IMPORT_BAZ'],
+  'repeatedStringPiece': ['224', '324'],
+  'repeatedCord': ['225', '325'],
+  'defaultInt32': 401,
+  'defaultInt64': '402',
+  'defaultUint32': 403,
+  'defaultUint64': '404',
+  'defaultSint32': 405,
+  'defaultSint64': '406',
+  'defaultFixed32': 407,
+  'defaultFixed64': '408',
+  'defaultSfixed32': 409,
+  'defaultSfixed64': '410',
+  'defaultFloat': 411.0,
+  'defaultDouble': 412.0,
+  'defaultBool': false,
+  'defaultString': '415',
+  'defaultBytes': 'NDE2',
+  'defaultNestedEnum': 'FOO',
+  'defaultForeignEnum': 'FOREIGN_FOO',
+  'defaultImportEnum': 'IMPORT_FOO',
+  'defaultStringPiece': '424',
+  'defaultCord': '425'
+};
+
+void main() {
+  group('encode', () {
+    test('testOutput', () {
+      expect(getAllSet().toProto3Json(), testAllTypesJson);
+    });
+
+    test('testUnsignedOutput', () {
+      TestAllTypes message = TestAllTypes();
+      // These values are selected because they are large enough to set the sign bit.
+      message.optionalUint64 = Int64.parseHex('f0000000ffff0000');
+      message.optionalFixed64 = Int64.parseHex('f0000000ffff0001');
+
+      expect(message.toProto3Json(), {
+        'optionalUint64': '17293822573397606400',
+        'optionalFixed64': '-1152921500311945215'
+      });
+    });
+
+    test('doubles', () {
+      void testValue(double value, Object expected) {
+        TestAllTypes message = TestAllTypes()
+          ..defaultFloat = value
+          ..defaultDouble = value;
+        expect(
+            (message.toProto3Json() as Map)['defaultDouble'], equals(expected));
+      }
+
+      testValue(-0.0, -0.0);
+      testValue(0.0, 0);
+      testValue(1.0, 1);
+      testValue(-1.0, -1);
+      testValue(double.nan, 'NaN');
+      testValue(double.infinity, 'Infinity');
+      testValue(double.negativeInfinity, '-Infinity');
+    });
+
+    test('map value', () {
+      TestMap message = TestMap()
+        ..int32ToInt32Field[32] = 32
+        ..int32ToStringField[0] = 'foo'
+        ..int32ToStringField[1] = 'bar'
+        ..int32ToBytesField[-1] = [1, 2, 3]
+        ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
+        ..int32ToMessageField[21] = (TestMap_MessageValue()
+          ..value = 2
+          ..secondValue = 3)
+        ..stringToInt32Field['key'] = -1
+        ..uint32ToInt32Field[0] = 0
+        ..int64ToInt32Field[Int64.ZERO] = 0
+        ..int64ToInt32Field[Int64.ONE] = 1
+        ..int64ToInt32Field[-Int64.ONE] = -1
+        ..int64ToInt32Field[Int64.MIN_VALUE] = -2
+        ..int64ToInt32Field[Int64.MAX_VALUE] = 2
+        ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
+      expect(message.toProto3Json(), {
+        'int32ToInt32Field': {'32': 32},
+        'int32ToStringField': {'0': 'foo', '1': 'bar'},
+        'int32ToBytesField': {'-1': 'AQID'},
+        'int32ToEnumField': {'1': 'BAZ'},
+        'int32ToMessageField': {
+          '21': {'value': 2, 'secondValue': 3}
+        },
+        'stringToInt32Field': {'key': -1},
+        'uint32ToInt32Field': {'0': 0},
+        'int64ToInt32Field': {
+          '0': 0,
+          '1': 1,
+          '-1': -1,
+          '-9223372036854775808': -2,
+          '9223372036854775807': 2
+        },
+        'uint64ToInt32Field': {'9223372036854775808': -2},
+      });
+    });
+
+    test('Timestamp', () {
+      expect(
+          Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17, 40))
+              .toProto3Json(),
+          '1969-07-20T20:17:40Z');
+      expect(Timestamp.fromDateTime(DateTime.utc(9)).toProto3Json(),
+          '0009-01-01T00:00:00Z');
+      expect(Timestamp.fromDateTime(DateTime.utc(420)).toProto3Json(),
+          '0420-01-01T00:00:00Z');
+      expect(() => Timestamp.fromDateTime(DateTime.utc(42001)).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(() => Timestamp.fromDateTime(DateTime.utc(-1)).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(
+          Timestamp.fromDateTime(DateTime.utc(9999, 12, 31, 23, 59, 59))
+              .toProto3Json(),
+          '9999-12-31T23:59:59Z');
+
+      expect((Timestamp()..nanos = 1).toProto3Json(),
+          '1970-01-01T00:00:00.000000001Z');
+      expect((Timestamp()..nanos = 8200000).toProto3Json(),
+          '1970-01-01T00:00:00.008200Z');
+      expect(() => (Timestamp()..nanos = -8200000).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(() => (Timestamp()..nanos = -8200000).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(() => (Timestamp()..nanos = 1000000000).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+    });
+
+    test('Duration', () {
+      expect(
+          (Duration()
+                ..seconds = Int64(0)
+                ..nanos = 0)
+              .toProto3Json(),
+          '0s');
+      expect(
+          (Duration()
+                ..seconds = Int64(10)
+                ..nanos = 0)
+              .toProto3Json(),
+          '10s');
+      expect(
+          (Duration()
+                ..seconds = Int64(10)
+                ..nanos = 1)
+              .toProto3Json(),
+          '10.000000001s');
+      expect(
+          (Duration()
+                ..seconds = Int64(10)
+                ..nanos = 10)
+              .toProto3Json(),
+          '10.00000001s');
+      expect(
+          (Duration()
+                ..seconds = -Int64(1)
+                ..nanos = -99000)
+              .toProto3Json(),
+          '-1.000099s');
+    });
+
+    test('Any', () {
+      expect(
+          Any.pack(TestAllTypes()..optionalFixed64 = Int64(100))
+              .toProto3Json(typeRegistry: TypeRegistry([TestAllTypes()])),
+          {
+            '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes',
+            'optionalFixed64': '100'
+          });
+      expect(
+          Any.pack(Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)))
+              .toProto3Json(typeRegistry: TypeRegistry([Timestamp()])),
+          {
+            '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+            'value': '1969-07-20T20:17:00Z'
+          });
+      expect(
+          () => Any.pack(Timestamp.fromDateTime(DateTime(1969, 7, 20, 20, 17)))
+              .toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+    });
+
+    test('struct', () {
+      final s = Struct()
+        ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
+        ..fields['number'] = (Value()..numberValue = 22.3)
+        ..fields['string'] = (Value()..stringValue = 'foo')
+        ..fields['bool'] = (Value()..boolValue = false)
+        ..fields['struct'] = (Value()
+          ..structValue =
+              (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
+        ..fields['list'] = (Value()
+          ..listValue = (ListValue()
+            ..values.addAll([
+              Value()..structValue = Struct(),
+              Value()..listValue = ListValue(),
+              Value()..stringValue = 'why'
+            ])));
+      expect(s.toProto3Json(), {
+        'null': null,
+        'number': 22.3,
+        'string': 'foo',
+        'bool': false,
+        'struct': {'a': 0},
+        'list': [{}, [], 'why']
+      });
+      expect(
+          () => Value().toProto3Json(), throwsA(TypeMatcher<ArgumentError>()));
+    });
+
+    test('empty', () {
+      expect(Empty().toProto3Json(), {});
+    });
+
+    test('wrapper types', () {
+      final t = TestWellKnownTypes()
+        ..doubleField = (DoubleValue()..value = 10.01)
+        ..floatField = (FloatValue()..value = 3.0)
+        ..int64Field = (Int64Value()..value = Int64.MIN_VALUE)
+        ..uint64Field = (UInt64Value()..value = Int64.MIN_VALUE)
+        ..int32Field = (Int32Value()..value = 101)
+        ..uint32Field = (UInt32Value()..value = 102)
+        ..boolField = (BoolValue()..value = false)
+        ..stringField = (StringValue()..value = 'Pop')
+        ..bytesField = (BytesValue()..value = [8, 9, 10]);
+      expect(t.toProto3Json(), {
+        'doubleField': 10.01,
+        'floatField': 3,
+        'int64Field': '-9223372036854775808',
+        'uint64Field': '9223372036854775808',
+        'int32Field': 101,
+        'uint32Field': 102,
+        'boolField': false,
+        'stringField': 'Pop',
+        'bytesField': 'CAkK',
+      });
+    });
+
+    test('field mask', () {
+      expect(FieldMask().toProto3Json(), '');
+      expect((FieldMask()..paths.addAll(['foo_bar_baz'])).toProto3Json(),
+          'fooBarBaz');
+      expect((FieldMask()..paths.addAll(['foo_bar', 'zop'])).toProto3Json(),
+          'fooBar,zop');
+      expect(() => (FieldMask()..paths.add('foo_3_bar')).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(() => (FieldMask()..paths.add('foo__bar')).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+      expect(() => (FieldMask()..paths.add('fooBar')).toProto3Json(),
+          throwsA(TypeMatcher<ArgumentError>()));
+    });
+  });
+
+  group('decode', () {
+    parseFailure(List<String> expectedPath) => throwsA(predicate((e) {
+          if (e is FormatException) {
+            final pathExpression =
+                RegExp(r'root(\["[^"]*"]*\])*').firstMatch(e.message)[0];
+            final actualPath = RegExp(r'\["([^"]*)"\]')
+                .allMatches(pathExpression)
+                .map((match) => match[1])
+                .toList();
+            if (actualPath.length != expectedPath.length) return false;
+            for (int i = 0; i < actualPath.length; i++) {
+              if (actualPath[i] != expectedPath[i]) return false;
+            }
+            return true;
+          }
+          return false;
+        }));
+
+    test('Nulls', () {
+      final decoded = TestAllTypes()
+        ..mergeFromProto3Json({'defaultString': null});
+      expect(decoded, TestAllTypes()..defaultString = 'hello');
+    });
+    test('decode TestAllTypes', () {
+      final decoded = TestAllTypes()..mergeFromProto3Json(testAllTypesJson);
+      expect(decoded, getAllSet());
+    });
+    test('Type expectations', () {
+      expect(
+          () => TestAllTypes()..mergeFromProto3Json({1: 1}), parseFailure([]));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBool': 1}),
+          parseFailure(['optionalBool']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBytes': 1}),
+          parseFailure(['optionalBytes']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalBytes': '()'}),
+          parseFailure(['optionalBytes']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt32': '()'}),
+          parseFailure(['optionalInt32']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt32': 20.4}),
+          parseFailure(['optionalInt32']));
+      expect(TestAllTypes()..mergeFromProto3Json({'optionalInt32': '28'}),
+          TestAllTypes()..optionalInt32 = 28);
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt64': '()'}),
+          parseFailure(['optionalInt64']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalInt64': 20.4}),
+          parseFailure(['optionalInt64']));
+      expect(TestAllTypes()..mergeFromProto3Json({'optionalInt64': '28'}),
+          TestAllTypes()..optionalInt64 = Int64(28));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
+          parseFailure(['optionalDouble']));
+      expect(TestAllTypes()..mergeFromProto3Json({'optionalDouble': 28}),
+          TestAllTypes()..optionalDouble = 28.0);
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalDouble': 'a'}),
+          parseFailure(['optionalDouble']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalString': 11}),
+          parseFailure(['optionalString']));
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({'optionalEnum': 'wrongValue'}),
+          parseFailure(['optionalEnum']));
+      expect(() => TestAllTypes()..mergeFromProto3Json({'optionalEnum': []}),
+          parseFailure(['optionalEnum']));
+      expect(
+          () =>
+              TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 100}),
+          parseFailure(['optionalNestedEnum']));
+      expect(
+          TestAllTypes()..mergeFromProto3Json({'optionalNestedEnum': 1}),
+          TestAllTypes()
+            ..optionalNestedEnum = TestAllTypes_NestedEnum.valueOf(1));
+      expect(TestAllTypes()..mergeFromProto3Json({'repeatedBool': null}),
+          TestAllTypes());
+      expect(() => TestAllTypes()..mergeFromProto3Json({'repeatedBool': 100}),
+          parseFailure(['repeatedBool']));
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'repeatedBool': [true, false, 1]
+            }),
+          parseFailure(['repeatedBool', '2']));
+      expect(() => TestAllTypes()..mergeFromProto3Json(Object()),
+          parseFailure([]));
+    });
+
+    test('merging behavior', () {
+      final t = TestAllTypes()
+        ..optionalForeignMessage = ForeignMessage()
+        ..repeatedForeignMessage.add(ForeignMessage()..c = 1);
+      final f = t.optionalForeignMessage;
+      expect(
+          t
+            ..mergeFromProto3Json({
+              'repeatedForeignMessage': [
+                {'c': 2}
+              ],
+              'optionalForeignMessage': {'c': 2}
+            }),
+          TestAllTypes()
+            ..optionalForeignMessage = (ForeignMessage()..c = 2)
+            ..repeatedForeignMessage
+                .addAll([ForeignMessage()..c = 1, ForeignMessage()..c = 2]));
+      expect(f, ForeignMessage()..c = 2);
+    });
+
+    test('names_with_underscores', () {
+      expect(
+          TestAllTypes()
+            ..mergeFromProto3Json({
+              'optional_foreign_message': {'c': 1}
+            }),
+          TestAllTypes()..optionalForeignMessage = (ForeignMessage()..c = 1));
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optional_foreign_message': {'c': 1}
+            }, supportNamesWithUnderscores: false),
+          parseFailure(['optional_foreign_message']));
+    });
+
+    test('map value', () {
+      TestMap expected = TestMap()
+        ..int32ToInt32Field[32] = 32
+        ..int32ToStringField[0] = 'foo'
+        ..int32ToStringField[1] = 'bar'
+        ..int32ToBytesField[-1] = [1, 2, 3]
+        ..int32ToEnumField[1] = TestMap_EnumValue.BAZ
+        ..int32ToMessageField[21] = (TestMap_MessageValue()
+          ..value = 2
+          ..secondValue = 3)
+        ..stringToInt32Field['key'] = -1
+        ..uint32ToInt32Field[0] = 0
+        ..int64ToInt32Field[Int64.ZERO] = 0
+        ..int64ToInt32Field[Int64.ONE] = 1
+        ..int64ToInt32Field[-Int64.ONE] = -1
+        ..int64ToInt32Field[Int64.MIN_VALUE] = -2
+        ..int64ToInt32Field[Int64.MAX_VALUE] = 2
+        ..uint64ToInt32Field[Int64.MIN_VALUE] = -2;
+      expect(
+          TestMap()
+            ..mergeFromProto3Json({
+              'int32ToInt32Field': {'32': 32},
+              'int32ToStringField': {'0': 'foo', '1': 'bar'},
+              'int32ToBytesField': {'-1': 'AQID'},
+              'int32ToEnumField': {'1': 'BAZ'},
+              'int32ToMessageField': {
+                '21': {'value': 2, 'secondValue': 3}
+              },
+              'stringToInt32Field': {'key': -1},
+              'uint32ToInt32Field': {'0': 0},
+              'int64ToInt32Field': {
+                '0': 0,
+                '1': 1,
+                '-1': -1,
+                '-9223372036854775808': -2,
+                '9223372036854775807': 2
+              },
+              'uint64ToInt32Field': {'9223372036854775808': -2},
+            }),
+          expected);
+      expect(() => TestMap()..mergeFromProto3Json([]), parseFailure([]));
+      expect(
+          () => TestMap()
+            ..mergeFromProto3Json({
+              'int32ToInt32Field': {'32': 'a'}
+            }),
+          parseFailure(['int32ToInt32Field', '32']));
+      expect(
+          () => TestMap()
+            ..mergeFromProto3Json({
+              'int32ToInt32Field': {'2147483648': 1}
+            }),
+          parseFailure(['int32ToInt32Field', '2147483648']));
+      expect(
+          () => TestMap()
+            ..mergeFromProto3Json({
+              'uint32ToInt32Field': {'-32': 21}
+            }),
+          parseFailure(['uint32ToInt32Field', '-32']));
+      expect(
+          () => TestMap()
+            ..mergeFromProto3Json({
+              'uint32ToInt32Field': {'4294967296': 21}
+            }),
+          parseFailure(['uint32ToInt32Field', '4294967296']));
+      expect(
+          TestMap()
+            ..mergeFromProto3Json({
+              'int32ToInt32Field': <dynamic, dynamic>{'2': 21}
+            }),
+          TestMap()..int32ToInt32Field[2] = 21);
+    });
+    test('ints', () {
+      expect(
+          (TestAllTypes()
+                ..mergeFromProto3Json({
+                  'optionalUint64': '17293822573397606400',
+                }))
+              .optionalUint64,
+          Int64.parseHex('f0000000ffff0000'));
+
+      // TODO(sigurdm): This should throw.
+      expect(
+          TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalUint64': '-1',
+            }),
+          TestAllTypes()
+            ..optionalUint64 =
+                Int64.fromBytes([255, 255, 255, 255, 255, 255, 255, 255]));
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalUint32': '-1',
+            }),
+          parseFailure(['optionalUint32']));
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalUint32': -1,
+            }),
+          parseFailure(['optionalUint32']));
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalUint32': 0xFFFFFFFF + 1,
+            }),
+          parseFailure(['optionalUint32']));
+
+      expect(
+          TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalInt32': '2147483647',
+            }),
+          TestAllTypes()..optionalInt32 = 2147483647);
+
+      expect(
+          TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalInt32': '-2147483648',
+            }),
+          TestAllTypes()..optionalInt32 = -2147483648);
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalInt32': 2147483647 + 1,
+            }),
+          parseFailure(['optionalInt32']));
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalInt32': (2147483647 + 1).toString(),
+            }),
+          parseFailure(['optionalInt32']));
+
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'optionalInt32': -2147483648 - 1,
+            }),
+          parseFailure(['optionalInt32']));
+    });
+
+    test('unknown fields', () {
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'myOwnInventedField': 'blahblahblah',
+            }),
+          throwsA(const TypeMatcher<FormatException>()));
+      expect(
+          () => TestAllTypes()
+            ..mergeFromProto3Json({
+              'myOwnInventedField': 'blahblahblah',
+            }, ignoreUnknownFields: false),
+          throwsA(const TypeMatcher<FormatException>()));
+      final t = TestAllTypes()
+        ..mergeFromProto3Json({
+          'myOwnInventedField': 'blahblahblah',
+        }, ignoreUnknownFields: true);
+      expect(t, TestAllTypes());
+      expect(t.unknownFields.isEmpty, isTrue);
+    });
+    test('Any', () {
+      final m1 = Any()
+        ..mergeFromProto3Json({
+          '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes',
+          'optionalFixed64': '100'
+        }, typeRegistry: TypeRegistry([TestAllTypes()]));
+
+      expect(m1.unpackInto(TestAllTypes()).optionalFixed64, Int64(100));
+
+      final m2 = Any()
+        ..mergeFromProto3Json({
+          '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+          'value': '1969-07-20T19:17:00Z'
+        }, typeRegistry: TypeRegistry([Timestamp()]));
+
+      expect(m2.unpackInto(Timestamp()).toDateTime().millisecondsSinceEpoch,
+          DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch);
+
+      expect(
+          () => Any()
+            ..mergeFromProto3Json({
+              '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+              'value': '1969-07-20T19:17:00Z'
+            }),
+          parseFailure([]));
+
+      expect(
+          () => Any()
+            ..mergeFromProto3Json(
+                {'@type': 11, 'value': '1969-07-20T19:17:00Z'}),
+          parseFailure([]));
+
+      final m3 = Any()
+        ..mergeFromProto3Json({
+          '@type': 'type.googleapis.com/google.protobuf.Any',
+          'value': {
+            '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+            'value': '1969-07-20T19:17:00Z'
+          }
+        }, typeRegistry: TypeRegistry([Timestamp(), Any()]));
+
+      expect(
+          m3
+              .unpackInto(Any())
+              .unpackInto(Timestamp())
+              .toDateTime()
+              .millisecondsSinceEpoch,
+          DateTime.utc(1969, 7, 20, 19, 17).millisecondsSinceEpoch);
+
+      // TODO(sigurdm): We would ideally like the error path to be
+      // ['value', 'value'].
+      expect(
+          () => Any()
+            ..mergeFromProto3Json({
+              '@type': 'type.googleapis.com/google.protobuf.Any',
+              'value': {
+                '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+                'value': '1969'
+              }
+            }, typeRegistry: TypeRegistry([Timestamp(), Any()])),
+          parseFailure([]));
+
+      expect(() => Any()..mergeFromProto3Json('@type'), parseFailure([]));
+
+      expect(() => Any()..mergeFromProto3Json(11), parseFailure([]));
+
+      expect(() => Any()..mergeFromProto3Json(['@type']), parseFailure([]));
+
+      expect(
+          () => Any()
+            ..mergeFromProto3Json({
+              '@type': 'type.googleapis.com/google.protobuf.Timestamp',
+              'value': '1969-07-20T19:17:00Z'
+            }),
+          parseFailure([]));
+    });
+
+    test('Duration', () {
+      expect(
+          Duration()..mergeFromProto3Json('0s'),
+          Duration()
+            ..seconds = Int64(0)
+            ..nanos = 0);
+      expect(
+          Duration()..mergeFromProto3Json('10s'),
+          Duration()
+            ..seconds = Int64(10)
+            ..nanos = 0);
+      expect(
+          Duration()..mergeFromProto3Json('10.000000001s'),
+          Duration()
+            ..seconds = Int64(10)
+            ..nanos = 1);
+      expect(
+          Duration()..mergeFromProto3Json('10.00000001s'),
+          Duration()
+            ..seconds = Int64(10)
+            ..nanos = 10);
+      expect(
+          Duration()..mergeFromProto3Json('-1.000099s'),
+          Duration()
+            ..seconds = -Int64(1)
+            ..nanos = -99000);
+
+      expect(
+          Duration()..mergeFromProto3Json('0.s'),
+          Duration()
+            ..seconds = Int64(0)
+            ..nanos = 0);
+      expect(
+          Duration()..mergeFromProto3Json('.0s'),
+          Duration()
+            ..seconds = Int64(0)
+            ..nanos = 0);
+      expect(
+          Duration()..mergeFromProto3Json('.5s'),
+          Duration()
+            ..seconds = Int64(0)
+            ..nanos = 500000000);
+      expect(
+          Duration()..mergeFromProto3Json('5.s'),
+          Duration()
+            ..seconds = Int64(5)
+            ..nanos = 0);
+      expect(
+          Duration()..mergeFromProto3Json('.s'),
+          Duration()
+            ..seconds = Int64(0)
+            ..nanos = 0);
+      expect(() => Duration()..mergeFromProto3Json('0.5'), parseFailure([]));
+      expect(() => Duration()..mergeFromProto3Json(100), parseFailure([]));
+    });
+
+    test('Timestamp', () {
+      expect(Timestamp()..mergeFromProto3Json('1969-07-20T20:17:00Z'),
+          Timestamp.fromDateTime(DateTime.utc(1969, 7, 20, 20, 17)));
+      expect(
+          Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.000000001Z'),
+          Timestamp()
+            ..seconds = Int64(0)
+            ..nanos = 1);
+      expect(Timestamp()..mergeFromProto3Json('1970-01-01T00:00:00.008200Z'),
+          Timestamp.fromDateTime(DateTime.utc(1970))..nanos = 8200000);
+      expect(
+          Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00-04:00'),
+          Timestamp()
+            ..seconds = Int64(82200)
+            ..nanos = 0);
+      expect(
+          Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00+04:00'),
+          Timestamp()
+            ..seconds = Int64(53400)
+            ..nanos = 0);
+      expect(
+          Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.0001+04:00'),
+          Timestamp()
+            ..seconds = Int64(53400)
+            ..nanos = 100000);
+      expect(
+          () => Timestamp()
+            ..mergeFromProto3Json('1970-01-01T00:00:00.0000000001Z'),
+          parseFailure([]));
+      expect(() => Timestamp()..mergeFromProto3Json(1970), parseFailure([]));
+      expect(() => Timestamp()..mergeFromProto3Json('1970-01-01T18:50:00.'),
+          parseFailure([]));
+    });
+
+    test('wrapper types', () {
+      expect(
+          TestWellKnownTypes()
+            ..mergeFromProto3Json({
+              'doubleField': 10.01,
+              'floatField': 3,
+              'int64Field': '-9223372036854775808',
+              'uint64Field': '9223372036854775808',
+              'int32Field': 101,
+              'uint32Field': 102,
+              'boolField': false,
+              'stringField': 'Pop',
+              'bytesField': 'CAkK',
+            }),
+          TestWellKnownTypes()
+            ..doubleField = (DoubleValue()..value = 10.01)
+            ..floatField = (FloatValue()..value = 3.0)
+            ..int64Field = (Int64Value()..value = Int64.MIN_VALUE)
+            ..uint64Field = (UInt64Value()..value = Int64.MIN_VALUE)
+            ..int32Field = (Int32Value()..value = 101)
+            ..uint32Field = (UInt32Value()..value = 102)
+            ..boolField = (BoolValue()..value = false)
+            ..stringField = (StringValue()..value = 'Pop')
+            ..bytesField = (BytesValue()..value = [8, 9, 10]));
+
+      expect(
+          TestWellKnownTypes()
+            ..mergeFromProto3Json({
+              'doubleField': '10.01',
+              'floatField': '3',
+              'int64Field': -854775808,
+              'uint64Field': 854775808,
+              'int32Field': '101',
+              'uint32Field': '102',
+              'boolField': false,
+            }),
+          TestWellKnownTypes()
+            ..doubleField = (DoubleValue()..value = 10.01)
+            ..floatField = (FloatValue()..value = 3.0)
+            ..int64Field = (Int64Value()..value = Int64(-854775808))
+            ..uint64Field = (UInt64Value()..value = Int64(854775808))
+            ..int32Field = (Int32Value()..value = 101)
+            ..uint32Field = (UInt32Value()..value = 102)
+            ..boolField = (BoolValue()..value = false),
+          reason: 'alternative representations should be accepted');
+
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'doubleField': 'a'}),
+          parseFailure(['doubleField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'doubleField': {}}),
+          parseFailure(['doubleField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'floatField': 'a'}),
+          parseFailure(['floatField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'floatField': {}}),
+          parseFailure(['floatField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': 'a'}),
+          parseFailure(['int64Field']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': {}}),
+          parseFailure(['int64Field']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'int64Field': 10.4}),
+          parseFailure(['int64Field']));
+
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'uint64Field': 'a'}),
+          parseFailure(['uint64Field']));
+      expect(
+          () =>
+              TestWellKnownTypes()..mergeFromProto3Json({'uint64Field': 10.4}),
+          parseFailure(['uint64Field']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'int32Field': 'a'}),
+          parseFailure(['int32Field']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'int32Field': 10.4}),
+          parseFailure(['int32Field']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'uint32Field': 'a'}),
+          parseFailure(['uint32Field']));
+      expect(
+          () =>
+              TestWellKnownTypes()..mergeFromProto3Json({'uint32Field': 10.4}),
+          parseFailure(['uint32Field']));
+      expect(
+          () =>
+              TestWellKnownTypes()..mergeFromProto3Json({'boolField': 'false'}),
+          parseFailure(['boolField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'stringField': 22}),
+          parseFailure(['stringField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'bytesField': 22}),
+          parseFailure(['bytesField']));
+      expect(
+          () => TestWellKnownTypes()..mergeFromProto3Json({'bytesField': '()'}),
+          parseFailure(['bytesField']));
+
+      expect(
+          TestWellKnownTypes()
+            ..mergeFromProto3Json({
+              'doubleField': null,
+              'floatField': null,
+              'int64Field': null,
+              'uint64Field': null,
+              'int32Field': null,
+              'uint32Field': null,
+              'boolField': null,
+              'stringField': null,
+              'bytesField': null,
+            }),
+          TestWellKnownTypes()
+            ..doubleField = DoubleValue()
+            ..floatField = FloatValue()
+            ..int64Field = Int64Value()
+            ..uint64Field = UInt64Value()
+            ..int32Field = Int32Value()
+            ..uint32Field = UInt32Value()
+            ..boolField = BoolValue()
+            ..stringField = StringValue()
+            ..bytesField = BytesValue(),
+          reason: 'having fields of wrapper types set to null will return an '
+              'empty wrapper (with unset .value field)');
+    });
+
+    test('struct', () {
+      final f = {
+        'null': null,
+        'number': 22.3,
+        'string': 'foo',
+        'bool': false,
+        'struct': {'a': 0},
+        'list': [{}, [], 'why']
+      };
+
+      final s = Struct()
+        ..fields['null'] = (Value()..nullValue = NullValue.NULL_VALUE)
+        ..fields['number'] = (Value()..numberValue = 22.3)
+        ..fields['string'] = (Value()..stringValue = 'foo')
+        ..fields['bool'] = (Value()..boolValue = false)
+        ..fields['struct'] = (Value()
+          ..structValue =
+              (Struct()..fields['a'] = (Value()..numberValue = 0.0)))
+        ..fields['list'] = (Value()
+          ..listValue = (ListValue()
+            ..values.addAll([
+              Value()..structValue = Struct(),
+              Value()..listValue = ListValue(),
+              Value()..stringValue = 'why'
+            ])));
+      expect(Struct()..mergeFromProto3Json(f), s);
+
+      expect(Struct()..mergeFromProto3Json(<dynamic, dynamic>{'a': 12}),
+          (Struct()..fields['a'] = (Value()..numberValue = 12.0)),
+          reason: 'Allow key type to be `dynamic`');
+
+      expect(() => Struct()..mergeFromProto3Json({1: 2}), parseFailure([]),
+          reason: 'Non-string key in JSON object literal');
+
+      expect(() => Struct()..mergeFromProto3Json([]), parseFailure([]),
+          reason: 'Non object literal');
+
+      expect(() => Struct()..mergeFromProto3Json([]), parseFailure([]),
+          reason: 'Non-string key in JSON object literal');
+
+      expect(() => Value()..mergeFromProto3Json(Object()), parseFailure([]),
+          reason: 'Non JSON value');
+
+      expect(() => ListValue()..mergeFromProto3Json({}), parseFailure([]),
+          reason: 'Non-list');
+    });
+
+    test('field mask', () {
+      expect(
+          TestWellKnownTypes()
+            ..mergeFromProto3Json({'fieldMaskField': 'foo,barBaz'}),
+          TestWellKnownTypes()
+            ..fieldMaskField = (FieldMask()..paths.addAll(['foo', 'bar_baz'])));
+      expect(() => FieldMask()..mergeFromProto3Json('foo,bar_bar'),
+          parseFailure([]));
+
+      expect(FieldMask()..mergeFromProto3Json(''), FieldMask());
+      expect(() => FieldMask()..mergeFromProto3Json(12), parseFailure([]));
+    });
+  });
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/api.proto b/protoc_plugin/test/protos/google/protobuf/api.proto
new file mode 100644
index 0000000..f37ee2f
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/api.proto
@@ -0,0 +1,210 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/source_context.proto";
+import "google/protobuf/type.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "ApiProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/api;api";
+
+// Api is a light-weight descriptor for an API Interface.
+//
+// Interfaces are also described as "protocol buffer services" in some contexts,
+// such as by the "service" keyword in a .proto file, but they are different
+// from API Services, which represent a concrete implementation of an interface
+// as opposed to simply a description of methods and bindings. They are also
+// sometimes simply referred to as "APIs" in other contexts, such as the name of
+// this message itself. See https://cloud.google.com/apis/design/glossary for
+// detailed terminology.
+message Api {
+
+  // The fully qualified name of this interface, including package name
+  // followed by the interface's simple name.
+  string name = 1;
+
+  // The methods of this interface, in unspecified order.
+  repeated Method methods = 2;
+
+  // Any metadata attached to the interface.
+  repeated Option options = 3;
+
+  // A version string for this interface. If specified, must have the form
+  // `major-version.minor-version`, as in `1.10`. If the minor version is
+  // omitted, it defaults to zero. If the entire version field is empty, the
+  // major version is derived from the package name, as outlined below. If the
+  // field is not empty, the version in the package name will be verified to be
+  // consistent with what is provided here.
+  //
+  // The versioning schema uses [semantic
+  // versioning](http://semver.org) where the major version number
+  // indicates a breaking change and the minor version an additive,
+  // non-breaking change. Both version numbers are signals to users
+  // what to expect from different versions, and should be carefully
+  // chosen based on the product plan.
+  //
+  // The major version is also reflected in the package name of the
+  // interface, which must end in `v<major-version>`, as in
+  // `google.feature.v1`. For major versions 0 and 1, the suffix can
+  // be omitted. Zero major versions must only be used for
+  // experimental, non-GA interfaces.
+  //
+  //
+  string version = 4;
+
+  // Source context for the protocol buffer service represented by this
+  // message.
+  SourceContext source_context = 5;
+
+  // Included interfaces. See [Mixin][].
+  repeated Mixin mixins = 6;
+
+  // The source syntax of the service.
+  Syntax syntax = 7;
+}
+
+// Method represents a method of an API interface.
+message Method {
+
+  // The simple name of this method.
+  string name = 1;
+
+  // A URL of the input message type.
+  string request_type_url = 2;
+
+  // If true, the request is streamed.
+  bool request_streaming = 3;
+
+  // The URL of the output message type.
+  string response_type_url = 4;
+
+  // If true, the response is streamed.
+  bool response_streaming = 5;
+
+  // Any metadata attached to the method.
+  repeated Option options = 6;
+
+  // The source syntax of this method.
+  Syntax syntax = 7;
+}
+
+// Declares an API Interface to be included in this interface. The including
+// interface must redeclare all the methods from the included interface, but
+// documentation and options are inherited as follows:
+//
+// - If after comment and whitespace stripping, the documentation
+//   string of the redeclared method is empty, it will be inherited
+//   from the original method.
+//
+// - Each annotation belonging to the service config (http,
+//   visibility) which is not set in the redeclared method will be
+//   inherited.
+//
+// - If an http annotation is inherited, the path pattern will be
+//   modified as follows. Any version prefix will be replaced by the
+//   version of the including interface plus the [root][] path if
+//   specified.
+//
+// Example of a simple mixin:
+//
+//     package google.acl.v1;
+//     service AccessControl {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v1/{resource=**}:getAcl";
+//       }
+//     }
+//
+//     package google.storage.v2;
+//     service Storage {
+//       rpc GetAcl(GetAclRequest) returns (Acl);
+//
+//       // Get a data record.
+//       rpc GetData(GetDataRequest) returns (Data) {
+//         option (google.api.http).get = "/v2/{resource=**}";
+//       }
+//     }
+//
+// Example of a mixin configuration:
+//
+//     apis:
+//     - name: google.storage.v2.Storage
+//       mixins:
+//       - name: google.acl.v1.AccessControl
+//
+// The mixin construct implies that all methods in `AccessControl` are
+// also declared with same name and request/response types in
+// `Storage`. A documentation generator or annotation processor will
+// see the effective `Storage.GetAcl` method after inherting
+// documentation and annotations as follows:
+//
+//     service Storage {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v2/{resource=**}:getAcl";
+//       }
+//       ...
+//     }
+//
+// Note how the version in the path pattern changed from `v1` to `v2`.
+//
+// If the `root` field in the mixin is specified, it should be a
+// relative path under which inherited HTTP paths are placed. Example:
+//
+//     apis:
+//     - name: google.storage.v2.Storage
+//       mixins:
+//       - name: google.acl.v1.AccessControl
+//         root: acls
+//
+// This implies the following inherited HTTP annotation:
+//
+//     service Storage {
+//       // Get the underlying ACL object.
+//       rpc GetAcl(GetAclRequest) returns (Acl) {
+//         option (google.api.http).get = "/v2/acls/{resource=**}:getAcl";
+//       }
+//       ...
+//     }
+message Mixin {
+  // The fully qualified name of the interface which is included.
+  string name = 1;
+
+  // If non-empty specifies a path under which inherited HTTP paths
+  // are rooted.
+  string root = 2;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/duration.proto b/protoc_plugin/test/protos/google/protobuf/duration.proto
new file mode 100644
index 0000000..975fce4
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/duration.proto
@@ -0,0 +1,117 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/duration";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "DurationProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// A Duration represents a signed, fixed-length span of time represented
+// as a count of seconds and fractions of seconds at nanosecond
+// resolution. It is independent of any calendar and concepts like "day"
+// or "month". It is related to Timestamp in that the difference between
+// two Timestamp values is a Duration and it can be added or subtracted
+// from a Timestamp. Range is approximately +-10,000 years.
+//
+// # Examples
+//
+// Example 1: Compute Duration from two Timestamps in pseudo code.
+//
+//     Timestamp start = ...;
+//     Timestamp end = ...;
+//     Duration duration = ...;
+//
+//     duration.seconds = end.seconds - start.seconds;
+//     duration.nanos = end.nanos - start.nanos;
+//
+//     if (duration.seconds < 0 && duration.nanos > 0) {
+//       duration.seconds += 1;
+//       duration.nanos -= 1000000000;
+//     } else if (durations.seconds > 0 && duration.nanos < 0) {
+//       duration.seconds -= 1;
+//       duration.nanos += 1000000000;
+//     }
+//
+// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code.
+//
+//     Timestamp start = ...;
+//     Duration duration = ...;
+//     Timestamp end = ...;
+//
+//     end.seconds = start.seconds + duration.seconds;
+//     end.nanos = start.nanos + duration.nanos;
+//
+//     if (end.nanos < 0) {
+//       end.seconds -= 1;
+//       end.nanos += 1000000000;
+//     } else if (end.nanos >= 1000000000) {
+//       end.seconds += 1;
+//       end.nanos -= 1000000000;
+//     }
+//
+// Example 3: Compute Duration from datetime.timedelta in Python.
+//
+//     td = datetime.timedelta(days=3, minutes=10)
+//     duration = Duration()
+//     duration.FromTimedelta(td)
+//
+// # JSON Mapping
+//
+// In JSON format, the Duration type is encoded as a string rather than an
+// object, where the string ends in the suffix "s" (indicating seconds) and
+// is preceded by the number of seconds, with nanoseconds expressed as
+// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+// microsecond should be expressed in JSON format as "3.000001s".
+//
+//
+message Duration {
+
+  // Signed seconds of the span of time. Must be from -315,576,000,000
+  // to +315,576,000,000 inclusive. Note: these bounds are computed from:
+  // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
+  int64 seconds = 1;
+
+  // Signed fractions of a second at nanosecond resolution of the span
+  // of time. Durations less than one second are represented with a 0
+  // `seconds` field and a positive or negative `nanos` field. For durations
+  // of one second or more, a non-zero value for the `nanos` field must be
+  // of the same sign as the `seconds` field. Must be from -999,999,999
+  // to +999,999,999 inclusive.
+  int32 nanos = 2;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/empty.proto b/protoc_plugin/test/protos/google/protobuf/empty.proto
new file mode 100644
index 0000000..03cacd2
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/empty.proto
@@ -0,0 +1,52 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option go_package = "github.com/golang/protobuf/ptypes/empty";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "EmptyProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option cc_enable_arenas = true;
+
+// A generic empty message that you can re-use to avoid defining duplicated
+// empty messages in your APIs. A typical example is to use it as the request
+// or the response type of an API method. For instance:
+//
+//     service Foo {
+//       rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty);
+//     }
+//
+// The JSON representation for `Empty` is empty JSON object `{}`.
+message Empty {}
diff --git a/protoc_plugin/test/protos/google/protobuf/field_mask.proto b/protoc_plugin/test/protos/google/protobuf/field_mask.proto
new file mode 100644
index 0000000..76e09f3
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/field_mask.proto
@@ -0,0 +1,252 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "FieldMaskProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/field_mask;field_mask";
+
+// `FieldMask` represents a set of symbolic field paths, for example:
+//
+//     paths: "f.a"
+//     paths: "f.b.d"
+//
+// Here `f` represents a field in some root message, `a` and `b`
+// fields in the message found in `f`, and `d` a field found in the
+// message in `f.b`.
+//
+// Field masks are used to specify a subset of fields that should be
+// returned by a get operation or modified by an update operation.
+// Field masks also have a custom JSON encoding (see below).
+//
+// # Field Masks in Projections
+//
+// When used in the context of a projection, a response message or
+// sub-message is filtered by the API to only contain those fields as
+// specified in the mask. For example, if the mask in the previous
+// example is applied to a response message as follows:
+//
+//     f {
+//       a : 22
+//       b {
+//         d : 1
+//         x : 2
+//       }
+//       y : 13
+//     }
+//     z: 8
+//
+// The result will not contain specific values for fields x,y and z
+// (their value will be set to the default, and omitted in proto text
+// output):
+//
+//
+//     f {
+//       a : 22
+//       b {
+//         d : 1
+//       }
+//     }
+//
+// A repeated field is not allowed except at the last position of a
+// paths string.
+//
+// If a FieldMask object is not present in a get operation, the
+// operation applies to all fields (as if a FieldMask of all fields
+// had been specified).
+//
+// Note that a field mask does not necessarily apply to the
+// top-level response message. In case of a REST get operation, the
+// field mask applies directly to the response, but in case of a REST
+// list operation, the mask instead applies to each individual message
+// in the returned resource list. In case of a REST custom method,
+// other definitions may be used. Where the mask applies will be
+// clearly documented together with its declaration in the API.  In
+// any case, the effect on the returned resource/resources is required
+// behavior for APIs.
+//
+// # Field Masks in Update Operations
+//
+// A field mask in update operations specifies which fields of the
+// targeted resource are going to be updated. The API is required
+// to only change the values of the fields as specified in the mask
+// and leave the others untouched. If a resource is passed in to
+// describe the updated values, the API ignores the values of all
+// fields not covered by the mask.
+//
+// If a repeated field is specified for an update operation, the existing
+// repeated values in the target resource will be overwritten by the new values.
+// Note that a repeated field is only allowed in the last position of a `paths`
+// string.
+//
+// If a sub-message is specified in the last position of the field mask for an
+// update operation, then the existing sub-message in the target resource is
+// overwritten. Given the target message:
+//
+//     f {
+//       b {
+//         d : 1
+//         x : 2
+//       }
+//       c : 1
+//     }
+//
+// And an update message:
+//
+//     f {
+//       b {
+//         d : 10
+//       }
+//     }
+//
+// then if the field mask is:
+//
+//  paths: "f.b"
+//
+// then the result will be:
+//
+//     f {
+//       b {
+//         d : 10
+//       }
+//       c : 1
+//     }
+//
+// However, if the update mask was:
+//
+//  paths: "f.b.d"
+//
+// then the result would be:
+//
+//     f {
+//       b {
+//         d : 10
+//         x : 2
+//       }
+//       c : 1
+//     }
+//
+// In order to reset a field's value to the default, the field must
+// be in the mask and set to the default value in the provided resource.
+// Hence, in order to reset all fields of a resource, provide a default
+// instance of the resource and set all fields in the mask, or do
+// not provide a mask as described below.
+//
+// If a field mask is not present on update, the operation applies to
+// all fields (as if a field mask of all fields has been specified).
+// Note that in the presence of schema evolution, this may mean that
+// fields the client does not know and has therefore not filled into
+// the request will be reset to their default. If this is unwanted
+// behavior, a specific service may require a client to always specify
+// a field mask, producing an error if not.
+//
+// As with get operations, the location of the resource which
+// describes the updated values in the request message depends on the
+// operation kind. In any case, the effect of the field mask is
+// required to be honored by the API.
+//
+// ## Considerations for HTTP REST
+//
+// The HTTP kind of an update operation which uses a field mask must
+// be set to PATCH instead of PUT in order to satisfy HTTP semantics
+// (PUT must only be used for full updates).
+//
+// # JSON Encoding of Field Masks
+//
+// In JSON, a field mask is encoded as a single string where paths are
+// separated by a comma. Fields name in each path are converted
+// to/from lower-camel naming conventions.
+//
+// As an example, consider the following message declarations:
+//
+//     message Profile {
+//       User user = 1;
+//       Photo photo = 2;
+//     }
+//     message User {
+//       string display_name = 1;
+//       string address = 2;
+//     }
+//
+// In proto a field mask for `Profile` may look as such:
+//
+//     mask {
+//       paths: "user.display_name"
+//       paths: "photo"
+//     }
+//
+// In JSON, the same mask is represented as below:
+//
+//     {
+//       mask: "user.displayName,photo"
+//     }
+//
+// # Field Masks and Oneof Fields
+//
+// Field masks treat fields in oneofs just as regular fields. Consider the
+// following message:
+//
+//     message SampleMessage {
+//       oneof test_oneof {
+//         string name = 4;
+//         SubMessage sub_message = 9;
+//       }
+//     }
+//
+// The field mask can be:
+//
+//     mask {
+//       paths: "name"
+//     }
+//
+// Or:
+//
+//     mask {
+//       paths: "sub_message"
+//     }
+//
+// Note that oneof type names ("test_oneof" in this case) cannot be used in
+// paths.
+//
+// ## Field Mask Verification
+//
+// The implementation of any API method which has a FieldMask type field in the
+// request should verify the included field paths, and return an
+// `INVALID_ARGUMENT` error if any path is duplicated or unmappable.
+message FieldMask {
+  // The set of field mask paths.
+  repeated string paths = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/source_context.proto b/protoc_plugin/test/protos/google/protobuf/source_context.proto
new file mode 100644
index 0000000..f3b2c96
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/source_context.proto
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "SourceContextProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/source_context;source_context";
+
+// `SourceContext` represents information about the source of a
+// protobuf element, like the file in which it is defined.
+message SourceContext {
+  // The path-qualified name of the .proto file that contained the associated
+  // protobuf element.  For example: `"google/protobuf/source_context.proto"`.
+  string file_name = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/struct.proto b/protoc_plugin/test/protos/google/protobuf/struct.proto
new file mode 100644
index 0000000..7d7808e
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/struct.proto
@@ -0,0 +1,96 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/struct;structpb";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "StructProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+
+// `Struct` represents a structured data value, consisting of fields
+// which map to dynamically typed values. In some languages, `Struct`
+// might be supported by a native representation. For example, in
+// scripting languages like JS a struct is represented as an
+// object. The details of that representation are described together
+// with the proto support for the language.
+//
+// The JSON representation for `Struct` is JSON object.
+message Struct {
+  // Unordered map of dynamically typed values.
+  map<string, Value> fields = 1;
+}
+
+// `Value` represents a dynamically typed value which can be either
+// null, a number, a string, a boolean, a recursive struct value, or a
+// list of values. A producer of value is expected to set one of that
+// variants, absence of any variant indicates an error.
+//
+// The JSON representation for `Value` is JSON value.
+message Value {
+  // The kind of value.
+  oneof kind {
+    // Represents a null value.
+    NullValue null_value = 1;
+    // Represents a double value.
+    double number_value = 2;
+    // Represents a string value.
+    string string_value = 3;
+    // Represents a boolean value.
+    bool bool_value = 4;
+    // Represents a structured value.
+    Struct struct_value = 5;
+    // Represents a repeated `Value`.
+    ListValue list_value = 6;
+  }
+}
+
+// `NullValue` is a singleton enumeration to represent the null value for the
+// `Value` type union.
+//
+//  The JSON representation for `NullValue` is JSON `null`.
+enum NullValue {
+  // Null value.
+  NULL_VALUE = 0;
+}
+
+// `ListValue` is a wrapper around a repeated field of values.
+//
+// The JSON representation for `ListValue` is JSON array.
+message ListValue {
+  // Repeated field of dynamically typed values.
+  repeated Value values = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/type.proto b/protoc_plugin/test/protos/google/protobuf/type.proto
new file mode 100644
index 0000000..624c15e
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/type.proto
@@ -0,0 +1,187 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+import "google/protobuf/any.proto";
+import "google/protobuf/source_context.proto";
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option java_package = "com.google.protobuf";
+option java_outer_classname = "TypeProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+option go_package = "google.golang.org/genproto/protobuf/ptype;ptype";
+
+// A protocol buffer message type.
+message Type {
+  // The fully qualified message name.
+  string name = 1;
+  // The list of fields.
+  repeated Field fields = 2;
+  // The list of types appearing in `oneof` definitions in this type.
+  repeated string oneofs = 3;
+  // The protocol buffer options.
+  repeated Option options = 4;
+  // The source context.
+  SourceContext source_context = 5;
+  // The source syntax.
+  Syntax syntax = 6;
+}
+
+// A single field of a message type.
+message Field {
+  // Basic field types.
+  enum Kind {
+    // Field type unknown.
+    TYPE_UNKNOWN        = 0;
+    // Field type double.
+    TYPE_DOUBLE         = 1;
+    // Field type float.
+    TYPE_FLOAT          = 2;
+    // Field type int64.
+    TYPE_INT64          = 3;
+    // Field type uint64.
+    TYPE_UINT64         = 4;
+    // Field type int32.
+    TYPE_INT32          = 5;
+    // Field type fixed64.
+    TYPE_FIXED64        = 6;
+    // Field type fixed32.
+    TYPE_FIXED32        = 7;
+    // Field type bool.
+    TYPE_BOOL           = 8;
+    // Field type string.
+    TYPE_STRING         = 9;
+    // Field type group. Proto2 syntax only, and deprecated.
+    TYPE_GROUP          = 10;
+    // Field type message.
+    TYPE_MESSAGE        = 11;
+    // Field type bytes.
+    TYPE_BYTES          = 12;
+    // Field type uint32.
+    TYPE_UINT32         = 13;
+    // Field type enum.
+    TYPE_ENUM           = 14;
+    // Field type sfixed32.
+    TYPE_SFIXED32       = 15;
+    // Field type sfixed64.
+    TYPE_SFIXED64       = 16;
+    // Field type sint32.
+    TYPE_SINT32         = 17;
+    // Field type sint64.
+    TYPE_SINT64         = 18;
+  };
+
+  // Whether a field is optional, required, or repeated.
+  enum Cardinality {
+    // For fields with unknown cardinality.
+    CARDINALITY_UNKNOWN = 0;
+    // For optional fields.
+    CARDINALITY_OPTIONAL = 1;
+    // For required fields. Proto2 syntax only.
+    CARDINALITY_REQUIRED = 2;
+    // For repeated fields.
+    CARDINALITY_REPEATED = 3;
+  };
+
+  // The field type.
+  Kind kind = 1;
+  // The field cardinality.
+  Cardinality cardinality = 2;
+  // The field number.
+  int32 number = 3;
+  // The field name.
+  string name = 4;
+  // The field type URL, without the scheme, for message or enumeration
+  // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`.
+  string type_url = 6;
+  // The index of the field type in `Type.oneofs`, for message or enumeration
+  // types. The first type has index 1; zero means the type is not in the list.
+  int32 oneof_index = 7;
+  // Whether to use alternative packed wire representation.
+  bool packed = 8;
+  // The protocol buffer options.
+  repeated Option options = 9;
+  // The field JSON name.
+  string json_name = 10;
+  // The string value of the default value of this field. Proto2 syntax only.
+  string default_value = 11;
+}
+
+// Enum type definition.
+message Enum {
+  // Enum type name.
+  string name = 1;
+  // Enum value definitions.
+  repeated EnumValue enumvalue = 2;
+  // Protocol buffer options.
+  repeated Option options = 3;
+  // The source context.
+  SourceContext source_context = 4;
+  // The source syntax.
+  Syntax syntax = 5;
+}
+
+// Enum value definition.
+message EnumValue {
+  // Enum value name.
+  string name = 1;
+  // Enum value number.
+  int32 number = 2;
+  // Protocol buffer options.
+  repeated Option options = 3;
+}
+
+// A protocol buffer option, which can be attached to a message, field,
+// enumeration, etc.
+message Option {
+  // The option's name. For protobuf built-in options (options defined in
+  // descriptor.proto), this is the short name. For example, `"map_entry"`.
+  // For custom options, it should be the fully-qualified name. For example,
+  // `"google.api.http"`.
+  string name = 1;
+  // The option's value packed in an Any message. If the value is a primitive,
+  // the corresponding wrapper type defined in google/protobuf/wrappers.proto
+  // should be used. If the value is an enum, it should be stored as an int32
+  // value using the google.protobuf.Int32Value type.
+  Any value = 2;
+}
+
+// The syntax in which a protocol buffer element is defined.
+enum Syntax {
+  // Syntax `proto2`.
+  SYNTAX_PROTO2 = 0;
+  // Syntax `proto3`.
+  SYNTAX_PROTO3 = 1;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto b/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto
new file mode 100644
index 0000000..c907524
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/unittest_well_known_types.proto
@@ -0,0 +1,114 @@
+syntax = "proto3";
+
+package protobuf_unittest;
+
+option csharp_namespace = "Google.Protobuf.TestProtos";
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.test";
+
+import "google/protobuf/any.proto";
+import "google/protobuf/api.proto";
+import "google/protobuf/duration.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/source_context.proto";
+import "google/protobuf/struct.proto";
+import "google/protobuf/timestamp.proto";
+import "google/protobuf/type.proto";
+import "google/protobuf/wrappers.proto";
+
+// Test that we can include all well-known types.
+// Each wrapper type is included separately, as languages
+// map handle different wrappers in different ways.
+message TestWellKnownTypes {
+  google.protobuf.Any any_field = 1;
+  google.protobuf.Api api_field = 2;
+  google.protobuf.Duration duration_field = 3;
+  google.protobuf.Empty empty_field = 4;
+  google.protobuf.FieldMask field_mask_field = 5;
+  google.protobuf.SourceContext source_context_field = 6;
+  google.protobuf.Struct struct_field = 7;
+  google.protobuf.Timestamp timestamp_field = 8;
+  google.protobuf.Type type_field = 9;
+  google.protobuf.DoubleValue double_field = 10;
+  google.protobuf.FloatValue float_field = 11;
+  google.protobuf.Int64Value int64_field = 12;
+  google.protobuf.UInt64Value uint64_field = 13;
+  google.protobuf.Int32Value int32_field = 14;
+  google.protobuf.UInt32Value uint32_field = 15;
+  google.protobuf.BoolValue bool_field = 16;
+  google.protobuf.StringValue string_field = 17;
+  google.protobuf.BytesValue bytes_field = 18;
+  // Part of struct, but useful to be able to test separately
+  google.protobuf.Value value_field = 19;
+}
+
+// A repeated field for each well-known type.
+message RepeatedWellKnownTypes {
+  repeated google.protobuf.Any any_field = 1;
+  repeated google.protobuf.Api api_field = 2;
+  repeated google.protobuf.Duration duration_field = 3;
+  repeated google.protobuf.Empty empty_field = 4;
+  repeated google.protobuf.FieldMask field_mask_field = 5;
+  repeated google.protobuf.SourceContext source_context_field = 6;
+  repeated google.protobuf.Struct struct_field = 7;
+  repeated google.protobuf.Timestamp timestamp_field = 8;
+  repeated google.protobuf.Type type_field = 9;
+  // These don't actually make a lot of sense, but they're not prohibited...
+  repeated google.protobuf.DoubleValue double_field = 10;
+  repeated google.protobuf.FloatValue float_field = 11;
+  repeated google.protobuf.Int64Value int64_field = 12;
+  repeated google.protobuf.UInt64Value uint64_field = 13;
+  repeated google.protobuf.Int32Value int32_field = 14;
+  repeated google.protobuf.UInt32Value uint32_field = 15;
+  repeated google.protobuf.BoolValue bool_field = 16;
+  repeated google.protobuf.StringValue string_field = 17;
+  repeated google.protobuf.BytesValue bytes_field = 18;
+}
+
+message OneofWellKnownTypes {
+  oneof oneof_field {
+    google.protobuf.Any any_field = 1;
+    google.protobuf.Api api_field = 2;
+    google.protobuf.Duration duration_field = 3;
+    google.protobuf.Empty empty_field = 4;
+    google.protobuf.FieldMask field_mask_field = 5;
+    google.protobuf.SourceContext source_context_field = 6;
+    google.protobuf.Struct struct_field = 7;
+    google.protobuf.Timestamp timestamp_field = 8;
+    google.protobuf.Type type_field = 9;
+    google.protobuf.DoubleValue double_field = 10;
+    google.protobuf.FloatValue float_field = 11;
+    google.protobuf.Int64Value int64_field = 12;
+    google.protobuf.UInt64Value uint64_field = 13;
+    google.protobuf.Int32Value int32_field = 14;
+    google.protobuf.UInt32Value uint32_field = 15;
+    google.protobuf.BoolValue bool_field = 16;
+    google.protobuf.StringValue string_field = 17;
+    google.protobuf.BytesValue bytes_field = 18;
+  }
+}
+
+// A map field for each well-known type. We only
+// need to worry about the value part of the map being the
+// well-known types, as messages can't be map keys.
+message MapWellKnownTypes {
+  map<int32,google.protobuf.Any> any_field = 1;
+  map<int32,google.protobuf.Api> api_field = 2;
+  map<int32,google.protobuf.Duration> duration_field = 3;
+  map<int32,google.protobuf.Empty> empty_field = 4;
+  map<int32,google.protobuf.FieldMask> field_mask_field = 5;
+  map<int32,google.protobuf.SourceContext> source_context_field = 6;
+  map<int32,google.protobuf.Struct> struct_field = 7;
+  map<int32,google.protobuf.Timestamp> timestamp_field = 8;
+  map<int32,google.protobuf.Type> type_field = 9;
+  map<int32,google.protobuf.DoubleValue> double_field = 10;
+  map<int32,google.protobuf.FloatValue> float_field = 11;
+  map<int32,google.protobuf.Int64Value> int64_field = 12;
+  map<int32,google.protobuf.UInt64Value> uint64_field = 13;
+  map<int32,google.protobuf.Int32Value> int32_field = 14;
+  map<int32,google.protobuf.UInt32Value> uint32_field = 15;
+  map<int32,google.protobuf.BoolValue> bool_field = 16;
+  map<int32,google.protobuf.StringValue> string_field = 17;
+  map<int32,google.protobuf.BytesValue> bytes_field = 18;
+}
diff --git a/protoc_plugin/test/protos/google/protobuf/wrappers.proto b/protoc_plugin/test/protos/google/protobuf/wrappers.proto
new file mode 100644
index 0000000..0194763
--- /dev/null
+++ b/protoc_plugin/test/protos/google/protobuf/wrappers.proto
@@ -0,0 +1,118 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Wrappers for primitive (non-message) types. These types are useful
+// for embedding primitives in the `google.protobuf.Any` type and for places
+// where we need to distinguish between the absence of a primitive
+// typed field and its default value.
+
+syntax = "proto3";
+
+package google.protobuf;
+
+option csharp_namespace = "Google.Protobuf.WellKnownTypes";
+option cc_enable_arenas = true;
+option go_package = "github.com/golang/protobuf/ptypes/wrappers";
+option java_package = "com.google.protobuf";
+option java_outer_classname = "WrappersProto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+// Wrapper message for `double`.
+//
+// The JSON representation for `DoubleValue` is JSON number.
+message DoubleValue {
+  // The double value.
+  double value = 1;
+}
+
+// Wrapper message for `float`.
+//
+// The JSON representation for `FloatValue` is JSON number.
+message FloatValue {
+  // The float value.
+  float value = 1;
+}
+
+// Wrapper message for `int64`.
+//
+// The JSON representation for `Int64Value` is JSON string.
+message Int64Value {
+  // The int64 value.
+  int64 value = 1;
+}
+
+// Wrapper message for `uint64`.
+//
+// The JSON representation for `UInt64Value` is JSON string.
+message UInt64Value {
+  // The uint64 value.
+  uint64 value = 1;
+}
+
+// Wrapper message for `int32`.
+//
+// The JSON representation for `Int32Value` is JSON number.
+message Int32Value {
+  // The int32 value.
+  int32 value = 1;
+}
+
+// Wrapper message for `uint32`.
+//
+// The JSON representation for `UInt32Value` is JSON number.
+message UInt32Value {
+  // The uint32 value.
+  uint32 value = 1;
+}
+
+// Wrapper message for `bool`.
+//
+// The JSON representation for `BoolValue` is JSON `true` and `false`.
+message BoolValue {
+  // The bool value.
+  bool value = 1;
+}
+
+// Wrapper message for `string`.
+//
+// The JSON representation for `StringValue` is JSON string.
+message StringValue {
+  // The string value.
+  string value = 1;
+}
+
+// Wrapper message for `bytes`.
+//
+// The JSON representation for `BytesValue` is JSON string.
+message BytesValue {
+  // The bytes value.
+  bytes value = 1;
+}
diff --git a/protoc_plugin/test/protos/map_field.proto b/protoc_plugin/test/protos/map_field.proto
index db2a606..0017d72 100644
--- a/protoc_plugin/test/protos/map_field.proto
+++ b/protoc_plugin/test/protos/map_field.proto
@@ -27,6 +27,7 @@
     map<string, int32>       string_to_int32_field = 6;
     map<uint32, int32>       uint32_to_int32_field = 7;
     map<int64, int32>        int64_to_int32_field = 8;
+    map<uint64, int32>       uint64_to_int32_field = 9;
 }
 
 message Inner  {