1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2008 Google Inc. All rights reserved. 4 // 5 // Use of this source code is governed by a BSD-style 6 // license that can be found in the LICENSE file or at 7 // https://developers.google.com/open-source/licenses/bsd 8 #endregion 9 10 using System; 11 using Google.Protobuf.TestProtos; 12 using NUnit.Framework; 13 using UnitTest.Issues.TestProtos; 14 using Google.Protobuf.WellKnownTypes; 15 using Google.Protobuf.Reflection; 16 17 using static Google.Protobuf.JsonParserTest; // For WrapInQuotes 18 using System.IO; 19 using Google.Protobuf.Collections; 20 using ProtobufUnittest; 21 22 namespace Google.Protobuf 23 { 24 /// <summary> 25 /// Tests for the JSON formatter. Note that in these tests, double quotes are replaced with apostrophes 26 /// for the sake of readability (embedding \" everywhere is painful). See the AssertJson method for details. 27 /// </summary> 28 public class JsonFormatterTest 29 { 30 [Test] DefaultValues_WhenOmitted()31 public void DefaultValues_WhenOmitted() 32 { 33 var formatter = JsonFormatter.Default; 34 35 AssertJson("{ }", formatter.Format(new ForeignMessage())); 36 AssertJson("{ }", formatter.Format(new TestAllTypes())); 37 AssertJson("{ }", formatter.Format(new TestMap())); 38 } 39 40 [Test] DefaultValues_WhenIncluded()41 public void DefaultValues_WhenIncluded() 42 { 43 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 44 AssertJson("{ 'c': 0 }", formatter.Format(new ForeignMessage())); 45 } 46 47 [Test] EnumAllowAlias()48 public void EnumAllowAlias() 49 { 50 var message = new TestEnumAllowAlias 51 { 52 Value = TestEnumWithDupValue.Foo2, 53 }; 54 var actualText = JsonFormatter.Default.Format(message); 55 var expectedText = "{ 'value': 'FOO1' }"; 56 AssertJson(expectedText, actualText); 57 } 58 59 [Test] EnumAsInt()60 public void EnumAsInt() 61 { 62 var message = new TestAllTypes 63 { 64 SingleForeignEnum = ForeignEnum.ForeignBar, 65 RepeatedForeignEnum = { ForeignEnum.ForeignBaz, (ForeignEnum) 100, ForeignEnum.ForeignFoo } 66 }; 67 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatEnumsAsIntegers(true)); 68 var actualText = formatter.Format(message); 69 var expectedText = "{ " + 70 "'singleForeignEnum': 5, " + 71 "'repeatedForeignEnum': [ 6, 100, 4 ]" + 72 " }"; 73 AssertJson(expectedText, actualText); 74 } 75 76 [Test] AllSingleFields()77 public void AllSingleFields() 78 { 79 var message = new TestAllTypes 80 { 81 SingleBool = true, 82 SingleBytes = ByteString.CopyFrom(1, 2, 3, 4), 83 SingleDouble = 23.5, 84 SingleFixed32 = 23, 85 SingleFixed64 = 1234567890123, 86 SingleFloat = 12.25f, 87 SingleForeignEnum = ForeignEnum.ForeignBar, 88 SingleForeignMessage = new ForeignMessage { C = 10 }, 89 SingleImportEnum = ImportEnum.ImportBaz, 90 SingleImportMessage = new ImportMessage { D = 20 }, 91 SingleInt32 = 100, 92 SingleInt64 = 3210987654321, 93 SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo, 94 SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 }, 95 SinglePublicImportMessage = new PublicImportMessage { E = 54 }, 96 SingleSfixed32 = -123, 97 SingleSfixed64 = -12345678901234, 98 SingleSint32 = -456, 99 SingleSint64 = -12345678901235, 100 SingleString = "test\twith\ttabs", 101 SingleUint32 = uint.MaxValue, 102 SingleUint64 = ulong.MaxValue, 103 }; 104 var actualText = JsonFormatter.Default.Format(message); 105 106 // Fields in numeric order 107 var expectedText = "{ " + 108 "'singleInt32': 100, " + 109 "'singleInt64': '3210987654321', " + 110 "'singleUint32': 4294967295, " + 111 "'singleUint64': '18446744073709551615', " + 112 "'singleSint32': -456, " + 113 "'singleSint64': '-12345678901235', " + 114 "'singleFixed32': 23, " + 115 "'singleFixed64': '1234567890123', " + 116 "'singleSfixed32': -123, " + 117 "'singleSfixed64': '-12345678901234', " + 118 "'singleFloat': 12.25, " + 119 "'singleDouble': 23.5, " + 120 "'singleBool': true, " + 121 "'singleString': 'test\\twith\\ttabs', " + 122 "'singleBytes': 'AQIDBA==', " + 123 "'singleNestedMessage': { 'bb': 35 }, " + 124 "'singleForeignMessage': { 'c': 10 }, " + 125 "'singleImportMessage': { 'd': 20 }, " + 126 "'singleNestedEnum': 'FOO', " + 127 "'singleForeignEnum': 'FOREIGN_BAR', " + 128 "'singleImportEnum': 'IMPORT_BAZ', " + 129 "'singlePublicImportMessage': { 'e': 54 }" + 130 " }"; 131 AssertJson(expectedText, actualText); 132 } 133 134 [Test] WithFormatDefaultValues_DoesNotAffectMessageFields()135 public void WithFormatDefaultValues_DoesNotAffectMessageFields() 136 { 137 var message = new TestAllTypes(); 138 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 139 var json = formatter.Format(message); 140 Assert.IsFalse(json.Contains("\"singleNestedMessage\"")); 141 Assert.IsFalse(json.Contains("\"singleForeignMessage\"")); 142 Assert.IsFalse(json.Contains("\"singleImportMessage\"")); 143 } 144 145 [Test] WithFormatDefaultValues_DoesNotAffectProto3OptionalFields()146 public void WithFormatDefaultValues_DoesNotAffectProto3OptionalFields() 147 { 148 var message = new TestProto3Optional { OptionalInt32 = 0 }; 149 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 150 var json = formatter.Format(message); 151 // The non-optional proto3 fields are formatted, as is the optional-but-specified field. 152 AssertJson("{ 'optionalInt32': 0, 'singularInt32': 0, 'singularInt64': '0' }", json); 153 } 154 155 [Test] WithFormatDefaultValues_DoesNotAffectProto2Fields()156 public void WithFormatDefaultValues_DoesNotAffectProto2Fields() 157 { 158 var message = new TestProtos.Proto2.ForeignMessage { C = 0 }; 159 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 160 var json = formatter.Format(message); 161 // The specified field is formatted, but the non-specified field (d) is not. 162 AssertJson("{ 'c': 0 }", json); 163 } 164 165 [Test] WithFormatDefaultValues_DoesNotAffectOneofFields()166 public void WithFormatDefaultValues_DoesNotAffectOneofFields() 167 { 168 var message = new TestOneof(); 169 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 170 var json = formatter.Format(message); 171 AssertJson("{ }", json); 172 } 173 174 [Test] RepeatedField()175 public void RepeatedField() 176 { 177 AssertJson("{ 'repeatedInt32': [ 1, 2, 3, 4, 5 ] }", 178 JsonFormatter.Default.Format(new TestAllTypes { RepeatedInt32 = { 1, 2, 3, 4, 5 } })); 179 } 180 181 [Test] MapField_StringString()182 public void MapField_StringString() 183 { 184 AssertJson("{ 'mapStringString': { 'with spaces': 'bar', 'a': 'b' } }", 185 JsonFormatter.Default.Format(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } })); 186 } 187 188 [Test] MapField_Int32Int32()189 public void MapField_Int32Int32() 190 { 191 // The keys are quoted, but the values aren't. 192 AssertJson("{ 'mapInt32Int32': { '0': 1, '2': 3 } }", 193 JsonFormatter.Default.Format(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } })); 194 } 195 196 [Test] MapField_BoolBool()197 public void MapField_BoolBool() 198 { 199 // The keys are quoted, but the values aren't. 200 AssertJson("{ 'mapBoolBool': { 'false': true, 'true': false } }", 201 JsonFormatter.Default.Format(new TestMap { MapBoolBool = { { false, true }, { true, false } } })); 202 } 203 204 [Test] NullValueOutsideStruct()205 public void NullValueOutsideStruct() 206 { 207 var message = new NullValueOutsideStruct { NullValue = NullValue.NullValue }; 208 AssertJson("{ 'nullValue': null }", JsonFormatter.Default.Format(message)); 209 } 210 211 [Test] NullValueNotInOneof()212 public void NullValueNotInOneof() 213 { 214 var message = new NullValueNotInOneof(); 215 AssertJson("{ }", JsonFormatter.Default.Format(message)); 216 } 217 218 [Test] NullValueNotInOneof_FormatDefaults()219 public void NullValueNotInOneof_FormatDefaults() 220 { 221 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 222 var message = new NullValueNotInOneof(); 223 AssertJson("{ 'nullValue': null }", formatter.Format(message)); 224 } 225 226 [TestCase(1.0, "1")] 227 [TestCase(double.NaN, "'NaN'")] 228 [TestCase(double.PositiveInfinity, "'Infinity'")] 229 [TestCase(double.NegativeInfinity, "'-Infinity'")] DoubleRepresentations(double value, string expectedValueText)230 public void DoubleRepresentations(double value, string expectedValueText) 231 { 232 var message = new TestAllTypes { SingleDouble = value }; 233 string actualText = JsonFormatter.Default.Format(message); 234 string expectedText = "{ 'singleDouble': " + expectedValueText + " }"; 235 AssertJson(expectedText, actualText); 236 } 237 238 [Test] UnknownEnumValueNumeric_SingleField()239 public void UnknownEnumValueNumeric_SingleField() 240 { 241 var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 100 }; 242 AssertJson("{ 'singleForeignEnum': 100 }", JsonFormatter.Default.Format(message)); 243 } 244 245 [Test] UnknownEnumValueNumeric_RepeatedField()246 public void UnknownEnumValueNumeric_RepeatedField() 247 { 248 var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.ForeignBaz, (ForeignEnum) 100, ForeignEnum.ForeignFoo } }; 249 AssertJson("{ 'repeatedForeignEnum': [ 'FOREIGN_BAZ', 100, 'FOREIGN_FOO' ] }", JsonFormatter.Default.Format(message)); 250 } 251 252 [Test] UnknownEnumValueNumeric_MapField()253 public void UnknownEnumValueNumeric_MapField() 254 { 255 var message = new TestMap { MapInt32Enum = { { 1, MapEnum.Foo }, { 2, (MapEnum) 100 }, { 3, MapEnum.Bar } } }; 256 AssertJson("{ 'mapInt32Enum': { '1': 'MAP_ENUM_FOO', '2': 100, '3': 'MAP_ENUM_BAR' } }", JsonFormatter.Default.Format(message)); 257 } 258 259 [Test] UnknownEnumValue_RepeatedField_AllEntriesUnknown()260 public void UnknownEnumValue_RepeatedField_AllEntriesUnknown() 261 { 262 var message = new TestAllTypes { RepeatedForeignEnum = { (ForeignEnum) 200, (ForeignEnum) 100 } }; 263 AssertJson("{ 'repeatedForeignEnum': [ 200, 100 ] }", JsonFormatter.Default.Format(message)); 264 } 265 266 [Test] 267 [TestCase("a\u17b4b", "a\\u17b4b")] // Explicit 268 [TestCase("a\u0601b", "a\\u0601b")] // Ranged 269 [TestCase("a\u0605b", "a\u0605b")] // Passthrough (note lack of double backslash...) SimpleNonAscii(string text, string encoded)270 public void SimpleNonAscii(string text, string encoded) 271 { 272 var message = new TestAllTypes { SingleString = text }; 273 AssertJson("{ 'singleString': '" + encoded + "' }", JsonFormatter.Default.Format(message)); 274 } 275 276 [Test] SurrogatePairEscaping()277 public void SurrogatePairEscaping() 278 { 279 var message = new TestAllTypes { SingleString = "a\uD801\uDC01b" }; 280 AssertJson("{ 'singleString': 'a\\ud801\\udc01b' }", JsonFormatter.Default.Format(message)); 281 } 282 283 [Test] InvalidSurrogatePairsFail()284 public void InvalidSurrogatePairsFail() 285 { 286 // Note: don't use TestCase for these, as the strings can't be reliably represented 287 // See http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/ 288 289 // Lone low surrogate 290 var message = new TestAllTypes { SingleString = "a\uDC01b" }; 291 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message)); 292 293 // Lone high surrogate 294 message = new TestAllTypes { SingleString = "a\uD801b" }; 295 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message)); 296 } 297 298 [Test] 299 [TestCase("foo_bar", "fooBar")] 300 [TestCase("bananaBanana", "bananaBanana")] 301 [TestCase("BANANABanana", "BANANABanana")] 302 [TestCase("simple", "simple")] 303 [TestCase("ACTION_AND_ADVENTURE", "ACTIONANDADVENTURE")] 304 [TestCase("action_and_adventure", "actionAndAdventure")] 305 [TestCase("kFoo", "kFoo")] 306 [TestCase("HTTPServer", "HTTPServer")] 307 [TestCase("CLIENT", "CLIENT")] ToJsonName(string original, string expected)308 public void ToJsonName(string original, string expected) 309 { 310 Assert.AreEqual(expected, JsonFormatter.ToJsonName(original)); 311 } 312 313 [Test] 314 [TestCase(null, "{ }")] 315 [TestCase("x", "{ 'fooString': 'x' }")] 316 [TestCase("", "{ 'fooString': '' }")] Oneof(string fooStringValue, string expectedJson)317 public void Oneof(string fooStringValue, string expectedJson) 318 { 319 var message = new TestOneof(); 320 if (fooStringValue != null) 321 { 322 message.FooString = fooStringValue; 323 } 324 325 // We should get the same result both with and without "format default values". 326 var formatter = JsonFormatter.Default; 327 AssertJson(expectedJson, formatter.Format(message)); 328 formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 329 AssertJson(expectedJson, formatter.Format(message)); 330 } 331 332 [Test] WrapperFormatting_Single()333 public void WrapperFormatting_Single() 334 { 335 // Just a few examples, handling both classes and value types, and 336 // default vs non-default values 337 var message = new TestWellKnownTypes 338 { 339 Int64Field = 10, 340 Int32Field = 0, 341 BytesField = ByteString.FromBase64("ABCD"), 342 StringField = "" 343 }; 344 var expectedJson = "{ 'int64Field': '10', 'int32Field': 0, 'stringField': '', 'bytesField': 'ABCD' }"; 345 AssertJson(expectedJson, JsonFormatter.Default.Format(message)); 346 } 347 348 [Test] WrapperFormatting_Message()349 public void WrapperFormatting_Message() 350 { 351 Assert.AreEqual("\"\"", JsonFormatter.Default.Format(new StringValue())); 352 Assert.AreEqual("0", JsonFormatter.Default.Format(new Int32Value())); 353 } 354 355 [Test] WrapperFormatting_FormatDefaultValuesDoesNotFormatNull()356 public void WrapperFormatting_FormatDefaultValuesDoesNotFormatNull() 357 { 358 // The actual JSON here is very large because there are lots of fields. Just test a couple of them. 359 var message = new TestWellKnownTypes { Int32Field = 10 }; 360 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 361 var actualJson = formatter.Format(message); 362 // This *used* to include "int64Field": null, but that was a bug. 363 // WithDefaultValues should not affect message fields, including wrapper types. 364 Assert.IsFalse(actualJson.Contains("\"int64Field\": null")); 365 Assert.IsTrue(actualJson.Contains("\"int32Field\": 10")); 366 } 367 368 [Test] OutputIsInNumericFieldOrder_NoDefaults()369 public void OutputIsInNumericFieldOrder_NoDefaults() 370 { 371 var formatter = JsonFormatter.Default; 372 var message = new TestJsonFieldOrdering { PlainString = "p1", PlainInt32 = 2 }; 373 AssertJson("{ 'plainString': 'p1', 'plainInt32': 2 }", formatter.Format(message)); 374 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" }; 375 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message)); 376 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" }; 377 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message)); 378 } 379 380 [Test] OutputIsInNumericFieldOrder_WithDefaults()381 public void OutputIsInNumericFieldOrder_WithDefaults() 382 { 383 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithFormatDefaultValues(true)); 384 var message = new TestJsonFieldOrdering(); 385 AssertJson("{ 'plainString': '', 'plainInt32': 0 }", formatter.Format(message)); 386 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" }; 387 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message)); 388 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" }; 389 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message)); 390 } 391 392 [Test] 393 [TestCase("1970-01-01T00:00:00Z", 0)] 394 [TestCase("1970-01-01T00:00:00.000000001Z", 1)] 395 [TestCase("1970-01-01T00:00:00.000000010Z", 10)] 396 [TestCase("1970-01-01T00:00:00.000000100Z", 100)] 397 [TestCase("1970-01-01T00:00:00.000001Z", 1000)] 398 [TestCase("1970-01-01T00:00:00.000010Z", 10000)] 399 [TestCase("1970-01-01T00:00:00.000100Z", 100000)] 400 [TestCase("1970-01-01T00:00:00.001Z", 1000000)] 401 [TestCase("1970-01-01T00:00:00.010Z", 10000000)] 402 [TestCase("1970-01-01T00:00:00.100Z", 100000000)] 403 [TestCase("1970-01-01T00:00:00.120Z", 120000000)] 404 [TestCase("1970-01-01T00:00:00.123Z", 123000000)] 405 [TestCase("1970-01-01T00:00:00.123400Z", 123400000)] 406 [TestCase("1970-01-01T00:00:00.123450Z", 123450000)] 407 [TestCase("1970-01-01T00:00:00.123456Z", 123456000)] 408 [TestCase("1970-01-01T00:00:00.123456700Z", 123456700)] 409 [TestCase("1970-01-01T00:00:00.123456780Z", 123456780)] 410 [TestCase("1970-01-01T00:00:00.123456789Z", 123456789)] TimestampStandalone(string expected, int nanos)411 public void TimestampStandalone(string expected, int nanos) 412 { 413 Assert.AreEqual(WrapInQuotes(expected), new Timestamp { Nanos = nanos }.ToString()); 414 } 415 416 [Test] TimestampStandalone_FromDateTime()417 public void TimestampStandalone_FromDateTime() 418 { 419 // One before and one after the Unix epoch, more easily represented via DateTime. 420 Assert.AreEqual("\"1673-06-19T12:34:56Z\"", 421 new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp().ToString()); 422 Assert.AreEqual("\"2015-07-31T10:29:34Z\"", 423 new DateTime(2015, 7, 31, 10, 29, 34, DateTimeKind.Utc).ToTimestamp().ToString()); 424 } 425 426 [Test] 427 [TestCase(-1, -1)] // Would be valid as duration 428 [TestCase(1, Timestamp.MaxNanos + 1)] 429 [TestCase(Timestamp.UnixSecondsAtBclMaxValue + 1, 0)] 430 [TestCase(Timestamp.UnixSecondsAtBclMinValue - 1, 0)] TimestampStandalone_NonNormalized(long seconds, int nanoseconds)431 public void TimestampStandalone_NonNormalized(long seconds, int nanoseconds) 432 { 433 var timestamp = new Timestamp { Seconds = seconds, Nanos = nanoseconds }; 434 Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(timestamp)); 435 } 436 437 [Test] TimestampField()438 public void TimestampField() 439 { 440 var message = new TestWellKnownTypes { TimestampField = new Timestamp() }; 441 AssertJson("{ 'timestampField': '1970-01-01T00:00:00Z' }", JsonFormatter.Default.Format(message)); 442 } 443 444 [Test] 445 [TestCase(0, 0, "0s")] 446 [TestCase(1, 0, "1s")] 447 [TestCase(-1, 0, "-1s")] 448 [TestCase(0, 1, "0.000000001s")] 449 [TestCase(0, 10, "0.000000010s")] 450 [TestCase(0, 100, "0.000000100s")] 451 [TestCase(0, 1000, "0.000001s")] 452 [TestCase(0, 10000, "0.000010s")] 453 [TestCase(0, 100000, "0.000100s")] 454 [TestCase(0, 1000000, "0.001s")] 455 [TestCase(0, 10000000, "0.010s")] 456 [TestCase(0, 100000000, "0.100s")] 457 [TestCase(0, 120000000, "0.120s")] 458 [TestCase(0, 123000000, "0.123s")] 459 [TestCase(0, 123400000, "0.123400s")] 460 [TestCase(0, 123450000, "0.123450s")] 461 [TestCase(0, 123456000, "0.123456s")] 462 [TestCase(0, 123456700, "0.123456700s")] 463 [TestCase(0, 123456780, "0.123456780s")] 464 [TestCase(0, 123456789, "0.123456789s")] 465 [TestCase(0, -100000000, "-0.100s")] 466 [TestCase(1, 100000000, "1.100s")] 467 [TestCase(-1, -100000000, "-1.100s")] DurationStandalone(long seconds, int nanoseconds, string expected)468 public void DurationStandalone(long seconds, int nanoseconds, string expected) 469 { 470 var json = JsonFormatter.Default.Format(new Duration { Seconds = seconds, Nanos = nanoseconds }); 471 Assert.AreEqual(WrapInQuotes(expected), json); 472 } 473 474 [Test] 475 [TestCase(1, 2123456789)] 476 [TestCase(1, -100000000)] DurationStandalone_NonNormalized(long seconds, int nanoseconds)477 public void DurationStandalone_NonNormalized(long seconds, int nanoseconds) 478 { 479 var duration = new Duration { Seconds = seconds, Nanos = nanoseconds }; 480 Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(duration)); 481 } 482 483 [Test] DurationField()484 public void DurationField() 485 { 486 var message = new TestWellKnownTypes { DurationField = new Duration() }; 487 AssertJson("{ 'durationField': '0s' }", JsonFormatter.Default.Format(message)); 488 } 489 490 [Test] StructSample()491 public void StructSample() 492 { 493 var message = new Struct 494 { 495 Fields = 496 { 497 { "a", Value.ForNull() }, 498 { "b", Value.ForBool(false) }, 499 { "c", Value.ForNumber(10.5) }, 500 { "d", Value.ForString("text") }, 501 { "e", Value.ForList(Value.ForString("t1"), Value.ForNumber(5)) }, 502 { "f", Value.ForStruct(new Struct { Fields = { { "nested", Value.ForString("value") } } }) } 503 } 504 }; 505 AssertJson("{ 'a': null, 'b': false, 'c': 10.5, 'd': 'text', 'e': [ 't1', 5 ], 'f': { 'nested': 'value' } }", message.ToString()); 506 } 507 508 [Test] 509 [TestCase("foo__bar")] 510 [TestCase("foo_3_ar")] 511 [TestCase("fooBar")] FieldMaskInvalid(string input)512 public void FieldMaskInvalid(string input) 513 { 514 var mask = new FieldMask { Paths = { input } }; 515 Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(mask)); 516 } 517 518 [Test] FieldMaskStandalone()519 public void FieldMaskStandalone() 520 { 521 var fieldMask = new FieldMask { Paths = { "", "single", "with_underscore", "nested.field.name", "nested..double_dot" } }; 522 Assert.AreEqual("\",single,withUnderscore,nested.field.name,nested..doubleDot\"", fieldMask.ToString()); 523 524 // Invalid, but we shouldn't create broken JSON... 525 fieldMask = new FieldMask { Paths = { "x\\y" } }; 526 Assert.AreEqual(@"""x\\y""", fieldMask.ToString()); 527 } 528 529 [Test] FieldMaskField()530 public void FieldMaskField() 531 { 532 var message = new TestWellKnownTypes { FieldMaskField = new FieldMask { Paths = { "user.display_name", "photo" } } }; 533 AssertJson("{ 'fieldMaskField': 'user.displayName,photo' }", JsonFormatter.Default.Format(message)); 534 } 535 536 // SourceContext is an example of a well-known type with no special JSON handling 537 [Test] SourceContextStandalone()538 public void SourceContextStandalone() 539 { 540 var message = new SourceContext { FileName = "foo.proto" }; 541 AssertJson("{ 'fileName': 'foo.proto' }", JsonFormatter.Default.Format(message)); 542 } 543 544 [Test] AnyWellKnownType()545 public void AnyWellKnownType() 546 { 547 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithTypeRegistry(TypeRegistry.FromMessages(Timestamp.Descriptor))); 548 var timestamp = new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp(); 549 var any = Any.Pack(timestamp); 550 AssertJson("{ '@type': 'type.googleapis.com/google.protobuf.Timestamp', 'value': '1673-06-19T12:34:56Z' }", formatter.Format(any)); 551 } 552 553 [Test] AnyMessageType()554 public void AnyMessageType() 555 { 556 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithTypeRegistry(TypeRegistry.FromMessages(TestAllTypes.Descriptor))); 557 var message = new TestAllTypes { SingleInt32 = 10, SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 } }; 558 var any = Any.Pack(message); 559 AssertJson("{ '@type': 'type.googleapis.com/protobuf_unittest3.TestAllTypes', 'singleInt32': 10, 'singleNestedMessage': { 'bb': 20 } }", formatter.Format(any)); 560 } 561 562 [Test] AnyMessageType_CustomPrefix()563 public void AnyMessageType_CustomPrefix() 564 { 565 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithTypeRegistry(TypeRegistry.FromMessages(TestAllTypes.Descriptor))); 566 var message = new TestAllTypes { SingleInt32 = 10 }; 567 var any = Any.Pack(message, "foo.bar/baz"); 568 AssertJson("{ '@type': 'foo.bar/baz/protobuf_unittest3.TestAllTypes', 'singleInt32': 10 }", formatter.Format(any)); 569 } 570 571 [Test] AnyNested()572 public void AnyNested() 573 { 574 var registry = TypeRegistry.FromMessages(TestWellKnownTypes.Descriptor, TestAllTypes.Descriptor); 575 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithTypeRegistry(registry)); 576 577 // Nest an Any as the value of an Any. 578 var doubleNestedMessage = new TestAllTypes { SingleInt32 = 20 }; 579 var nestedMessage = Any.Pack(doubleNestedMessage); 580 var message = new TestWellKnownTypes { AnyField = Any.Pack(nestedMessage) }; 581 AssertJson("{ 'anyField': { '@type': 'type.googleapis.com/google.protobuf.Any', 'value': { '@type': 'type.googleapis.com/protobuf_unittest3.TestAllTypes', 'singleInt32': 20 } } }", 582 formatter.Format(message)); 583 } 584 585 [Test] AnyUnknownType()586 public void AnyUnknownType() 587 { 588 // The default type registry doesn't have any types in it. 589 var message = new TestAllTypes(); 590 var any = Any.Pack(message); 591 Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(any)); 592 } 593 594 [Test] 595 [TestCase(typeof(BoolValue), true, "true")] 596 [TestCase(typeof(Int32Value), 32, "32")] 597 [TestCase(typeof(Int64Value), 32L, "\"32\"")] 598 [TestCase(typeof(UInt32Value), 32U, "32")] 599 [TestCase(typeof(UInt64Value), 32UL, "\"32\"")] 600 [TestCase(typeof(StringValue), "foo", "\"foo\"")] 601 [TestCase(typeof(FloatValue), 1.5f, "1.5")] 602 [TestCase(typeof(DoubleValue), 1.5d, "1.5")] Wrappers_Standalone(System.Type wrapperType, object value, string expectedJson)603 public void Wrappers_Standalone(System.Type wrapperType, object value, string expectedJson) 604 { 605 IMessage populated = (IMessage)Activator.CreateInstance(wrapperType); 606 populated.Descriptor.Fields[WrappersReflection.WrapperValueFieldNumber].Accessor.SetValue(populated, value); 607 Assert.AreEqual(expectedJson, JsonFormatter.Default.Format(populated)); 608 } 609 610 // See See https://github.com/protocolbuffers/protobuf/issues/11987 611 [Test] JsonNamePriority()612 public void JsonNamePriority() 613 { 614 // This tests both the formatter and the parser, but the issue was when parsing. 615 var original = new Issue11987Message { A = 10, B = 20, C = 30 }; 616 var json = JsonFormatter.Default.Format(original); 617 AssertJson("{ 'b': 10, 'a': 20, 'd': 30 }", json); 618 var parsed = Issue11987Message.Parser.ParseJson(json); 619 Assert.AreEqual(original, parsed); 620 } 621 622 // Sanity tests for WriteValue. Not particularly comprehensive, as it's all covered above already, 623 // as FormatMessage uses WriteValue. 624 625 [TestCase(null, "null")] 626 [TestCase(1, "1")] 627 [TestCase(1L, "'1'")] 628 [TestCase(0.5f, "0.5")] 629 [TestCase(0.5d, "0.5")] 630 [TestCase("text", "'text'")] 631 [TestCase("x\ny", @"'x\ny'")] 632 [TestCase(ForeignEnum.ForeignBar, "'FOREIGN_BAR'")] WriteValue_Constant(object value, string expectedJson)633 public void WriteValue_Constant(object value, string expectedJson) 634 { 635 AssertWriteValue(value, expectedJson); 636 } 637 638 [Test] WriteValue_Timestamp()639 public void WriteValue_Timestamp() 640 { 641 var value = new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp(); 642 AssertWriteValue(value, "'1673-06-19T12:34:56Z'"); 643 } 644 645 [Test] WriteValue_Message()646 public void WriteValue_Message() 647 { 648 var value = new TestAllTypes { SingleInt32 = 100, SingleInt64 = 3210987654321L }; 649 AssertWriteValue(value, "{ 'singleInt32': 100, 'singleInt64': '3210987654321' }"); 650 } 651 652 [Test] WriteValue_Message_PreserveNames()653 public void WriteValue_Message_PreserveNames() 654 { 655 var value = new TestAllTypes { SingleInt32 = 100, SingleInt64 = 3210987654321L }; 656 AssertWriteValue(value, "{ 'single_int32': 100, 'single_int64': '3210987654321' }", JsonFormatter.Settings.Default.WithPreserveProtoFieldNames(true)); 657 } 658 659 [Test] WriteValue_List()660 public void WriteValue_List() 661 { 662 var value = new RepeatedField<int> { 1, 2, 3 }; 663 AssertWriteValue(value, "[ 1, 2, 3 ]"); 664 } 665 666 [Test] WriteValueWithIndentation_EmptyMessage()667 public void WriteValueWithIndentation_EmptyMessage() 668 { 669 var value = new TestEmptyMessage(); 670 671 AssertWriteValue(value, "{}", JsonFormatter.Settings.Default.WithIndentation()); 672 } 673 674 [Test] WriteValueWithIndentation_NestedTestAllTypes()675 public void WriteValueWithIndentation_NestedTestAllTypes() 676 { 677 var value = new NestedTestAllTypes 678 { 679 Payload = new TestAllTypes 680 { 681 SingleBool = true, 682 SingleInt32 = 100, 683 SingleString = "multiple fields", 684 RepeatedString = { "string1", "string2" }, 685 }, 686 Child = new NestedTestAllTypes 687 { 688 Payload = new TestAllTypes 689 { 690 SingleString = "single field", 691 }, 692 }, 693 RepeatedChild = 694 { 695 new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 1", RepeatedString = { "string" } } }, 696 new NestedTestAllTypes { Payload = new TestAllTypes { SingleString = "child 2" } }, 697 }, 698 }; 699 700 const string expectedJson = @" 701 { 702 'child': { 703 'payload': { 704 'singleString': 'single field' 705 } 706 }, 707 'payload': { 708 'singleInt32': 100, 709 'singleBool': true, 710 'singleString': 'multiple fields', 711 'repeatedString': [ 712 'string1', 713 'string2' 714 ] 715 }, 716 'repeatedChild': [ 717 { 718 'payload': { 719 'singleString': 'child 1', 720 'repeatedString': [ 721 'string' 722 ] 723 } 724 }, 725 { 726 'payload': { 727 'singleString': 'child 2' 728 } 729 } 730 ] 731 }"; 732 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 733 } 734 735 [Test] WriteValueWithIndentation_WellKnownTypes()736 public void WriteValueWithIndentation_WellKnownTypes() 737 { 738 var value = new TestWellKnownTypes 739 { 740 StructField = new Struct 741 { 742 Fields = 743 { 744 { "string", Value.ForString("foo") }, 745 { "numbers", Value.ForList(Value.ForNumber(1), Value.ForNumber(2), Value.ForNumber(3)) }, 746 { "emptyList", Value.ForList() }, 747 { "emptyStruct", Value.ForStruct(new Struct()) }, 748 }, 749 }, 750 }; 751 752 const string expectedJson = @" 753 { 754 'structField': { 755 'string': 'foo', 756 'numbers': [ 757 1, 758 2, 759 3 760 ], 761 'emptyList': [], 762 'emptyStruct': {} 763 } 764 }"; 765 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 766 } 767 768 [Test] WriteValueWithIndentation_StructSingleField()769 public void WriteValueWithIndentation_StructSingleField() 770 { 771 var value = new Struct { Fields = { { "structField1", Value.ForString("structFieldValue1") } } }; 772 773 const string expectedJson = @" 774 { 775 'structField1': 'structFieldValue1' 776 }"; 777 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 778 } 779 780 [Test] WriteValueWithIndentation_StructMultipleFields()781 public void WriteValueWithIndentation_StructMultipleFields() 782 { 783 var value = new Struct 784 { 785 Fields = 786 { 787 { "structField1", Value.ForString("structFieldValue1") }, 788 { "structField2", Value.ForString("structFieldValue2") }, 789 { "structField3", Value.ForString("structFieldValue3") }, 790 }, 791 }; 792 793 const string expectedJson = @" 794 { 795 'structField1': 'structFieldValue1', 796 'structField2': 'structFieldValue2', 797 'structField3': 'structFieldValue3' 798 }"; 799 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 800 } 801 802 [Test] FormatWithIndentation_EmbeddedMessage()803 public void FormatWithIndentation_EmbeddedMessage() 804 { 805 var value = new TestAllTypes { SingleInt32 = 100, SingleInt64 = 3210987654321L }; 806 var formatter = new JsonFormatter(JsonFormatter.Settings.Default.WithIndentation()); 807 var valueJson = formatter.Format(value, indentationLevel: 1); 808 809 var actualJson = $@" 810 {{ 811 ""data"": {valueJson} 812 }}"; 813 const string expectedJson = @" 814 { 815 'data': { 816 'singleInt32': 100, 817 'singleInt64': '3210987654321' 818 } 819 }"; 820 AssertJson(expectedJson, actualJson.Trim()); 821 } 822 823 [Test] WriteValueWithIndentation_Map()824 public void WriteValueWithIndentation_Map() 825 { 826 var value = new TestMap 827 { 828 MapStringString = 829 { 830 { "key1", "value1" }, 831 { "key2", "value2" }, 832 }, 833 }; 834 835 const string expectedJson = @" 836 { 837 'mapStringString': { 838 'key1': 'value1', 839 'key2': 'value2' 840 } 841 }"; 842 843 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 844 } 845 846 [Test] WriteValueWithIndentation_MapWithNested()847 public void WriteValueWithIndentation_MapWithNested() 848 { 849 var value = new TestMap 850 { 851 MapInt32ForeignMessage = 852 { 853 { 1, new ForeignMessage { C = 1 } }, 854 { 2, new ForeignMessage { C = 2 } }, 855 }, 856 }; 857 858 const string expectedJson = @" 859 { 860 'mapInt32ForeignMessage': { 861 '1': { 862 'c': 1 863 }, 864 '2': { 865 'c': 2 866 } 867 } 868 }"; 869 870 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 871 } 872 873 [Test] WriteValueWithIndentation_MapWithEmptyNested()874 public void WriteValueWithIndentation_MapWithEmptyNested() 875 { 876 var value = new TestMap 877 { 878 MapInt32ForeignMessage = 879 { 880 { 1, new ForeignMessage() }, 881 { 2, new ForeignMessage() }, 882 }, 883 }; 884 885 const string expectedJson = @" 886 { 887 'mapInt32ForeignMessage': { 888 '1': {}, 889 '2': {} 890 } 891 }"; 892 893 AssertWriteValue(value, expectedJson, JsonFormatter.Settings.Default.WithIndentation()); 894 } 895 896 [Test] WriteValueWithIndentation_List()897 public void WriteValueWithIndentation_List() 898 { 899 var value = new RepeatedField<int> { 1, 2, 3 }; 900 AssertWriteValue(value, "[\n 1,\n 2,\n 3\n]", JsonFormatter.Settings.Default.WithIndentation()); 901 } 902 903 [Test] WriteValueWithIndentation_Any()904 public void WriteValueWithIndentation_Any() 905 { 906 var registry = TypeRegistry.FromMessages(ForeignMessage.Descriptor); 907 var formatter = JsonFormatter.Settings.Default.WithIndentation().WithTypeRegistry(registry); 908 909 var nestedMessage = new ForeignMessage { C = 1 }; 910 var value = Any.Pack(nestedMessage); 911 const string expectedJson = @" 912 { 913 '@type': 'type.googleapis.com/protobuf_unittest3.ForeignMessage', 914 'c': 1 915 }"; 916 917 AssertWriteValue(value, expectedJson, formatter); 918 } 919 920 [Test] WriteValueWithIndentation_NestedAny()921 public void WriteValueWithIndentation_NestedAny() 922 { 923 var registry = TypeRegistry.FromMessages(ForeignMessage.Descriptor); 924 var formatter = JsonFormatter.Settings.Default.WithIndentation().WithTypeRegistry(registry); 925 926 var nestedMessage = new ForeignMessage { C = 1 }; 927 var value = new TestWellKnownTypes { AnyField = Any.Pack(nestedMessage) }; 928 const string expectedJson = @" 929 { 930 'anyField': { 931 '@type': 'type.googleapis.com/protobuf_unittest3.ForeignMessage', 932 'c': 1 933 } 934 }"; 935 936 AssertWriteValue(value, expectedJson, formatter); 937 } 938 939 [Test] WriteValueWithIndentation_CustomIndentation()940 public void WriteValueWithIndentation_CustomIndentation() 941 { 942 var value = new RepeatedField<int> { 1, 2, 3 }; 943 AssertWriteValue(value, "[\n\t1,\n\t2,\n\t3\n]", JsonFormatter.Settings.Default.WithIndentation("\t")); 944 } 945 946 [Test] Proto2_DefaultValuesWritten()947 public void Proto2_DefaultValuesWritten() 948 { 949 var value = new ProtobufTestMessages.Proto2.TestAllTypesProto2() { FieldName13 = 0 }; 950 AssertWriteValue(value, "{ 'FieldName13': 0 }"); 951 } 952 AssertWriteValue(object value, string expectedJson, JsonFormatter.Settings settings = null)953 private static void AssertWriteValue(object value, string expectedJson, JsonFormatter.Settings settings = null) 954 { 955 var writer = new StringWriter { NewLine = "\n" }; 956 new JsonFormatter(settings ?? JsonFormatter.Settings.Default).WriteValue(writer, value); 957 string actual = writer.ToString(); 958 AssertJson(expectedJson, actual); 959 } 960 961 /// <summary> 962 /// Checks that the actual JSON is the same as the expected JSON - but after replacing 963 /// all apostrophes in the expected JSON with double quotes, trimming leading whitespace and normalizing new lines. 964 /// This basically makes the tests easier to read. 965 /// </summary> 966 /// <remarks> 967 /// Line endings are normalized because indented JSON strings are generated with system-specific line endings, 968 /// while line endings in the test cases are hard-coded, but may be converted during source checkout, depending 969 /// on git settings, causing unpredictability in the test results otherwise.</remarks> AssertJson(string expectedJsonWithApostrophes, string actualJson)970 private static void AssertJson(string expectedJsonWithApostrophes, string actualJson) 971 { 972 var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"").Replace("\r\n", "\n").TrimStart(); 973 Assert.AreEqual(expectedJson, actualJson.Replace("\r\n", "\n")); 974 } 975 } 976 } 977