Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions protobuf/lib/protobuf.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'dart:typed_data' show TypedData, Uint8List, ByteData, Endian;
import 'package:fixnum/fixnum.dart' show Int64;

import 'src/protobuf/json_parsing_context.dart';
import 'src/protobuf/json_serialization_context.dart';
import 'src/protobuf/permissive_compare.dart';
import 'src/protobuf/type_registry.dart';
export 'src/protobuf/type_registry.dart' show TypeRegistry;
Expand Down
5 changes: 3 additions & 2 deletions protobuf/lib/src/protobuf/generated_message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ abstract class GeneratedMessage {
/// message encoding a type not in [typeRegistry] is encountered, an
/// error is thrown.
Object? toProto3Json(
{TypeRegistry typeRegistry = const TypeRegistry.empty()}) =>
_writeToProto3Json(_fieldSet, typeRegistry);
{TypeRegistry typeRegistry = const TypeRegistry.empty(),
bool emitDefaults = false}) =>
_writeToProto3Json(_fieldSet, typeRegistry, emitDefaults);

/// Merges field values from [json], a JSON object using proto3 encoding.
///
Expand Down
9 changes: 9 additions & 0 deletions protobuf/lib/src/protobuf/json_serialization_context.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) 2022, 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 JsonSerializationContext {
final bool emitDefaults;

JsonSerializationContext(this.emitDefaults);
}
42 changes: 37 additions & 5 deletions protobuf/lib/src/protobuf/proto3_json.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

part of protobuf;

Object? _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
Object? _writeToProto3Json(
_FieldSet fs, TypeRegistry typeRegistry, bool emitDefaults) {
var context = JsonSerializationContext(emitDefaults);

String? convertToMapKey(dynamic key, int keyType) {
var baseType = PbFieldType._baseType(keyType);

Expand Down Expand Up @@ -36,8 +39,8 @@ Object? _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
if (fieldValue == null) return null;

if (_isGroupOrMessage(fieldType!)) {
return _writeToProto3Json(
(fieldValue as GeneratedMessage)._fieldSet, typeRegistry);
return _writeToProto3Json((fieldValue as GeneratedMessage)._fieldSet,
typeRegistry, context.emitDefaults);
} else if (_isEnum(fieldType)) {
return (fieldValue as ProtobufEnum).name;
} else {
Expand Down Expand Up @@ -81,6 +84,14 @@ Object? _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
}
}

bool isNullOrEmptyList(dynamic value) {
return value == null || (value is List && value.isEmpty);
}

bool isNullOrEmptyMap(dynamic value) {
return value == null || (value is Map && value.isEmpty);
}

final meta = fs._meta;
if (meta.toProto3Json != null) {
return meta.toProto3Json!(fs._message!, typeRegistry);
Expand All @@ -89,11 +100,32 @@ Object? _writeToProto3Json(_FieldSet fs, TypeRegistry typeRegistry) {
var result = <String, dynamic>{};
for (var fieldInfo in fs._infosSortedByTag) {
var value = fs._values[fieldInfo.index!];
if (value == null || (value is List && value.isEmpty)) {
var overrideForEmitsDefaults = false;
dynamic overrideForEmitsDefaultsValue;
if (context.emitDefaults) {
if (fieldInfo.isRepeated && isNullOrEmptyList(value)) {
overrideForEmitsDefaults = true;
overrideForEmitsDefaultsValue = [];
} else if (fieldInfo.isMapField && isNullOrEmptyMap(value)) {
overrideForEmitsDefaults = true;
overrideForEmitsDefaultsValue = {};
} else if (_isBytes(fieldInfo.type) && isNullOrEmptyList(value)) {
overrideForEmitsDefaults = true;
overrideForEmitsDefaultsValue = null;
} else if (_isGroupOrMessage(fieldInfo.type) && value == null) {
overrideForEmitsDefaults = true;
overrideForEmitsDefaultsValue = null;
} else {
value ??= fieldInfo.makeDefault!();
}
}
if (isNullOrEmptyList(value) && !overrideForEmitsDefaults) {
continue; // It's missing, repeated, or an empty byte array.
}
dynamic jsonValue;
if (fieldInfo.isMapField) {
if (overrideForEmitsDefaults) {
jsonValue = overrideForEmitsDefaultsValue;
} else if (fieldInfo.isMapField) {
jsonValue = (value as PbMap).map((key, entryValue) {
var mapEntryInfo = fieldInfo as MapFieldInfo;
return MapEntry(convertToMapKey(key, mapEntryInfo.keyFieldType!),
Expand Down
58 changes: 58 additions & 0 deletions protobuf/test/json_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,64 @@ void main() {
final decoded = T()..mergeFromJsonMap(encoded);
expect(decoded.int64, value);
});

test('testToProto3Json', () {
var json = jsonEncode(example.toProto3Json());
checkProto3JsonMap(jsonDecode(json), 3);
});

test('testToProto3JsonEmitDefaults', () {
var json = jsonEncode(example.toProto3Json(emitDefaults: true));
checkProto3JsonMap(jsonDecode(json), 6);
expect(json.contains('"child":null'), isTrue);
});

test('testToProto3JsonEmitDefaultsNoValues', () {
final exampleAllDefaults = T();
var json = jsonEncode(exampleAllDefaults.toProto3Json(emitDefaults: true));
Map m = jsonDecode(json);
expect(m.length, 6);
});

test('testToProto3JsonEmitDefaultsWithChild', () {
var child = example;

var parent = T()
..val = 123
..str = 'hello'
..int32s.addAll(<int>[1, 2, 3])
..child = example;
var parentJson = jsonEncode(parent.toProto3Json(emitDefaults: true));
var childJson = jsonEncode(child.toProto3Json(emitDefaults: true));
checkProto3JsonMap(jsonDecode(parentJson), 6);
expect(parentJson.contains(childJson), isTrue);
});

test('testToProto3JsonEmitDefaultsWithNullList', () {
var exampleEmptyList = T()
..val = example.val
..str = example.str;

var json = jsonEncode(exampleEmptyList.toProto3Json(emitDefaults: true));
expect(json.contains('"int32s":[]'), isTrue);
});

test('testToProto3JsonEmitDefaultsWithEmptyList', () {
var exampleEmptyList = T()
..val = example.val
..str = example.str
..int32s.addAll(<int>[]);

var json = jsonEncode(exampleEmptyList.toProto3Json(emitDefaults: true));
expect(json.contains('"int32s":[]'), isTrue);
});
}

void checkProto3JsonMap(Map m, int expectedLength) {
expect(m.length, expectedLength);
expect(m['val'], 123);
expect(m['str'], 'hello');
expect(m['int32s'], [1, 2, 3]);
}

void checkJsonMap(Map m) {
Expand Down