| OLD | NEW |
| (Empty) | |
| 1 #region Copyright notice and license |
| 2 // Protocol Buffers - Google's data interchange format |
| 3 // Copyright 2008 Google Inc. All rights reserved. |
| 4 // https://developers.google.com/protocol-buffers/ |
| 5 // |
| 6 // Redistribution and use in source and binary forms, with or without |
| 7 // modification, are permitted provided that the following conditions are |
| 8 // met: |
| 9 // |
| 10 // * Redistributions of source code must retain the above copyright |
| 11 // notice, this list of conditions and the following disclaimer. |
| 12 // * Redistributions in binary form must reproduce the above |
| 13 // copyright notice, this list of conditions and the following disclaimer |
| 14 // in the documentation and/or other materials provided with the |
| 15 // distribution. |
| 16 // * Neither the name of Google Inc. nor the names of its |
| 17 // contributors may be used to endorse or promote products derived from |
| 18 // this software without specific prior written permission. |
| 19 // |
| 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 #endregion |
| 32 |
| 33 using System; |
| 34 using Google.Protobuf.TestProtos; |
| 35 using NUnit.Framework; |
| 36 using UnitTest.Issues.TestProtos; |
| 37 using Google.Protobuf.WellKnownTypes; |
| 38 |
| 39 namespace Google.Protobuf |
| 40 { |
| 41 /// <summary> |
| 42 /// Tests for the JSON formatter. Note that in these tests, double quotes ar
e replaced with apostrophes |
| 43 /// for the sake of readability (embedding \" everywhere is painful). See th
e AssertJson method for details. |
| 44 /// </summary> |
| 45 public class JsonFormatterTest |
| 46 { |
| 47 [Test] |
| 48 public void DefaultValues_WhenOmitted() |
| 49 { |
| 50 var formatter = new JsonFormatter(new JsonFormatter.Settings(formatD
efaultValues: false)); |
| 51 |
| 52 AssertJson("{ }", formatter.Format(new ForeignMessage())); |
| 53 AssertJson("{ }", formatter.Format(new TestAllTypes())); |
| 54 AssertJson("{ }", formatter.Format(new TestMap())); |
| 55 } |
| 56 |
| 57 [Test] |
| 58 public void DefaultValues_WhenIncluded() |
| 59 { |
| 60 var formatter = new JsonFormatter(new JsonFormatter.Settings(formatD
efaultValues: true)); |
| 61 AssertJson("{ 'c': 0 }", formatter.Format(new ForeignMessage())); |
| 62 } |
| 63 |
| 64 [Test] |
| 65 public void AllSingleFields() |
| 66 { |
| 67 var message = new TestAllTypes |
| 68 { |
| 69 SingleBool = true, |
| 70 SingleBytes = ByteString.CopyFrom(1, 2, 3, 4), |
| 71 SingleDouble = 23.5, |
| 72 SingleFixed32 = 23, |
| 73 SingleFixed64 = 1234567890123, |
| 74 SingleFloat = 12.25f, |
| 75 SingleForeignEnum = ForeignEnum.FOREIGN_BAR, |
| 76 SingleForeignMessage = new ForeignMessage { C = 10 }, |
| 77 SingleImportEnum = ImportEnum.IMPORT_BAZ, |
| 78 SingleImportMessage = new ImportMessage { D = 20 }, |
| 79 SingleInt32 = 100, |
| 80 SingleInt64 = 3210987654321, |
| 81 SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO, |
| 82 SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb
= 35 }, |
| 83 SinglePublicImportMessage = new PublicImportMessage { E = 54 }, |
| 84 SingleSfixed32 = -123, |
| 85 SingleSfixed64 = -12345678901234, |
| 86 SingleSint32 = -456, |
| 87 SingleSint64 = -12345678901235, |
| 88 SingleString = "test\twith\ttabs", |
| 89 SingleUint32 = uint.MaxValue, |
| 90 SingleUint64 = ulong.MaxValue, |
| 91 }; |
| 92 var actualText = JsonFormatter.Default.Format(message); |
| 93 |
| 94 // Fields in numeric order |
| 95 var expectedText = "{ " + |
| 96 "'singleInt32': 100, " + |
| 97 "'singleInt64': '3210987654321', " + |
| 98 "'singleUint32': 4294967295, " + |
| 99 "'singleUint64': '18446744073709551615', " + |
| 100 "'singleSint32': -456, " + |
| 101 "'singleSint64': '-12345678901235', " + |
| 102 "'singleFixed32': 23, " + |
| 103 "'singleFixed64': '1234567890123', " + |
| 104 "'singleSfixed32': -123, " + |
| 105 "'singleSfixed64': '-12345678901234', " + |
| 106 "'singleFloat': 12.25, " + |
| 107 "'singleDouble': 23.5, " + |
| 108 "'singleBool': true, " + |
| 109 "'singleString': 'test\\twith\\ttabs', " + |
| 110 "'singleBytes': 'AQIDBA==', " + |
| 111 "'singleNestedMessage': { 'bb': 35 }, " + |
| 112 "'singleForeignMessage': { 'c': 10 }, " + |
| 113 "'singleImportMessage': { 'd': 20 }, " + |
| 114 "'singleNestedEnum': 'FOO', " + |
| 115 "'singleForeignEnum': 'FOREIGN_BAR', " + |
| 116 "'singleImportEnum': 'IMPORT_BAZ', " + |
| 117 "'singlePublicImportMessage': { 'e': 54 }" + |
| 118 " }"; |
| 119 AssertJson(expectedText, actualText); |
| 120 } |
| 121 |
| 122 [Test] |
| 123 public void RepeatedField() |
| 124 { |
| 125 AssertJson("{ 'repeatedInt32': [ 1, 2, 3, 4, 5 ] }", |
| 126 JsonFormatter.Default.Format(new TestAllTypes { RepeatedInt32 =
{ 1, 2, 3, 4, 5 } })); |
| 127 } |
| 128 |
| 129 [Test] |
| 130 public void MapField_StringString() |
| 131 { |
| 132 AssertJson("{ 'mapStringString': { 'with spaces': 'bar', 'a': 'b' }
}", |
| 133 JsonFormatter.Default.Format(new TestMap { MapStringString = { {
"with spaces", "bar" }, { "a", "b" } } })); |
| 134 } |
| 135 |
| 136 [Test] |
| 137 public void MapField_Int32Int32() |
| 138 { |
| 139 // The keys are quoted, but the values aren't. |
| 140 AssertJson("{ 'mapInt32Int32': { '0': 1, '2': 3 } }", |
| 141 JsonFormatter.Default.Format(new TestMap { MapInt32Int32 = { { 0
, 1 }, { 2, 3 } } })); |
| 142 } |
| 143 |
| 144 [Test] |
| 145 public void MapField_BoolBool() |
| 146 { |
| 147 // The keys are quoted, but the values aren't. |
| 148 AssertJson("{ 'mapBoolBool': { 'false': true, 'true': false } }", |
| 149 JsonFormatter.Default.Format(new TestMap { MapBoolBool = { { fal
se, true }, { true, false } } })); |
| 150 } |
| 151 |
| 152 [TestCase(1.0, "1")] |
| 153 [TestCase(double.NaN, "'NaN'")] |
| 154 [TestCase(double.PositiveInfinity, "'Infinity'")] |
| 155 [TestCase(double.NegativeInfinity, "'-Infinity'")] |
| 156 public void DoubleRepresentations(double value, string expectedValueText
) |
| 157 { |
| 158 var message = new TestAllTypes { SingleDouble = value }; |
| 159 string actualText = JsonFormatter.Default.Format(message); |
| 160 string expectedText = "{ 'singleDouble': " + expectedValueText + " }
"; |
| 161 AssertJson(expectedText, actualText); |
| 162 } |
| 163 |
| 164 [Test] |
| 165 public void UnknownEnumValueOmitted_SingleField() |
| 166 { |
| 167 var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 1
00 }; |
| 168 AssertJson("{ }", JsonFormatter.Default.Format(message)); |
| 169 } |
| 170 |
| 171 [Test] |
| 172 public void UnknownEnumValueOmitted_RepeatedField() |
| 173 { |
| 174 var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum
.FOREIGN_BAZ, (ForeignEnum) 100, ForeignEnum.FOREIGN_FOO } }; |
| 175 AssertJson("{ 'repeatedForeignEnum': [ 'FOREIGN_BAZ', 'FOREIGN_FOO'
] }", JsonFormatter.Default.Format(message)); |
| 176 } |
| 177 |
| 178 [Test] |
| 179 public void UnknownEnumValueOmitted_MapField() |
| 180 { |
| 181 // This matches the C++ behaviour. |
| 182 var message = new TestMap { MapInt32Enum = { { 1, MapEnum.MAP_ENUM_F
OO }, { 2, (MapEnum) 100 }, { 3, MapEnum.MAP_ENUM_BAR } } }; |
| 183 AssertJson("{ 'mapInt32Enum': { '1': 'MAP_ENUM_FOO', '3': 'MAP_ENUM_
BAR' } }", JsonFormatter.Default.Format(message)); |
| 184 } |
| 185 |
| 186 [Test] |
| 187 public void UnknownEnumValueOmitted_RepeatedField_AllEntriesUnknown() |
| 188 { |
| 189 // *Maybe* we should hold off on writing the "[" until we find that
we've got at least one value to write... |
| 190 // but this is what happens at the moment, and it doesn't seem too a
wful. |
| 191 var message = new TestAllTypes { RepeatedForeignEnum = { (ForeignEnu
m) 200, (ForeignEnum) 100 } }; |
| 192 AssertJson("{ 'repeatedForeignEnum': [ ] }", JsonFormatter.Default.F
ormat(message)); |
| 193 } |
| 194 |
| 195 [Test] |
| 196 public void NullValueForMessage() |
| 197 { |
| 198 var message = new TestMap { MapInt32ForeignMessage = { { 10, null }
} }; |
| 199 AssertJson("{ 'mapInt32ForeignMessage': { '10': null } }", JsonForma
tter.Default.Format(message)); |
| 200 } |
| 201 |
| 202 [Test] |
| 203 [TestCase("a\u17b4b", "a\\u17b4b")] // Explicit |
| 204 [TestCase("a\u0601b", "a\\u0601b")] // Ranged |
| 205 [TestCase("a\u0605b", "a\u0605b")] // Passthrough (note lack of double b
ackslash...) |
| 206 public void SimpleNonAscii(string text, string encoded) |
| 207 { |
| 208 var message = new TestAllTypes { SingleString = text }; |
| 209 AssertJson("{ 'singleString': '" + encoded + "' }", JsonFormatter.De
fault.Format(message)); |
| 210 } |
| 211 |
| 212 [Test] |
| 213 public void SurrogatePairEscaping() |
| 214 { |
| 215 var message = new TestAllTypes { SingleString = "a\uD801\uDC01b" }; |
| 216 AssertJson("{ 'singleString': 'a\\ud801\\udc01b' }", JsonFormatter.D
efault.Format(message)); |
| 217 } |
| 218 |
| 219 [Test] |
| 220 public void InvalidSurrogatePairsFail() |
| 221 { |
| 222 // Note: don't use TestCase for these, as the strings can't be relia
bly represented |
| 223 // See http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a
-string/ |
| 224 |
| 225 // Lone low surrogate |
| 226 var message = new TestAllTypes { SingleString = "a\uDC01b" }; |
| 227 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(
message)); |
| 228 |
| 229 // Lone high surrogate |
| 230 message = new TestAllTypes { SingleString = "a\uD801b" }; |
| 231 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(
message)); |
| 232 } |
| 233 |
| 234 [Test] |
| 235 [TestCase("foo_bar", "fooBar")] |
| 236 [TestCase("bananaBanana", "bananaBanana")] |
| 237 [TestCase("BANANABanana", "bananaBanana")] |
| 238 public void ToCamelCase(string original, string expected) |
| 239 { |
| 240 Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original)); |
| 241 } |
| 242 |
| 243 [Test] |
| 244 [TestCase(null, "{ }")] |
| 245 [TestCase("x", "{ 'fooString': 'x' }")] |
| 246 [TestCase("", "{ 'fooString': '' }")] |
| 247 [TestCase(null, "{ }")] |
| 248 public void Oneof(string fooStringValue, string expectedJson) |
| 249 { |
| 250 var message = new TestOneof(); |
| 251 if (fooStringValue != null) |
| 252 { |
| 253 message.FooString = fooStringValue; |
| 254 } |
| 255 |
| 256 // We should get the same result both with and without "format defau
lt values". |
| 257 var formatter = new JsonFormatter(new JsonFormatter.Settings(false))
; |
| 258 AssertJson(expectedJson, formatter.Format(message)); |
| 259 formatter = new JsonFormatter(new JsonFormatter.Settings(true)); |
| 260 AssertJson(expectedJson, formatter.Format(message)); |
| 261 } |
| 262 |
| 263 [Test] |
| 264 public void WrapperFormatting_Single() |
| 265 { |
| 266 // Just a few examples, handling both classes and value types, and |
| 267 // default vs non-default values |
| 268 var message = new TestWellKnownTypes |
| 269 { |
| 270 Int64Field = 10, |
| 271 Int32Field = 0, |
| 272 BytesField = ByteString.FromBase64("ABCD"), |
| 273 StringField = "" |
| 274 }; |
| 275 var expectedJson = "{ 'int64Field': '10', 'int32Field': 0, 'stringFi
eld': '', 'bytesField': 'ABCD' }"; |
| 276 AssertJson(expectedJson, JsonFormatter.Default.Format(message)); |
| 277 } |
| 278 |
| 279 [Test] |
| 280 public void WrapperFormatting_IncludeNull() |
| 281 { |
| 282 // The actual JSON here is very large because there are lots of fiel
ds. Just test a couple of them. |
| 283 var message = new TestWellKnownTypes { Int32Field = 10 }; |
| 284 var formatter = new JsonFormatter(new JsonFormatter.Settings(true)); |
| 285 var actualJson = formatter.Format(message); |
| 286 Assert.IsTrue(actualJson.Contains("\"int64Field\": null")); |
| 287 Assert.IsFalse(actualJson.Contains("\"int32Field\": null")); |
| 288 } |
| 289 |
| 290 [Test] |
| 291 public void OutputIsInNumericFieldOrder_NoDefaults() |
| 292 { |
| 293 var formatter = new JsonFormatter(new JsonFormatter.Settings(false))
; |
| 294 var message = new TestJsonFieldOrdering { PlainString = "p1", PlainI
nt32 = 2 }; |
| 295 AssertJson("{ 'plainString': 'p1', 'plainInt32': 2 }", formatter.For
mat(message)); |
| 296 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2",
PlainInt32 = 10, PlainString = "plain" }; |
| 297 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32'
: 10, 'o1Int32': 5 }", formatter.Format(message)); |
| 298 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, Pl
ainInt32 = 10, PlainString = "plain" }; |
| 299 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32':
10, 'o2Int32': 0 }", formatter.Format(message)); |
| 300 } |
| 301 |
| 302 [Test] |
| 303 public void OutputIsInNumericFieldOrder_WithDefaults() |
| 304 { |
| 305 var formatter = new JsonFormatter(new JsonFormatter.Settings(true)); |
| 306 var message = new TestJsonFieldOrdering(); |
| 307 AssertJson("{ 'plainString': '', 'plainInt32': 0 }", formatter.Forma
t(message)); |
| 308 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2",
PlainInt32 = 10, PlainString = "plain" }; |
| 309 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32'
: 10, 'o1Int32': 5 }", formatter.Format(message)); |
| 310 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, Pl
ainInt32 = 10, PlainString = "plain" }; |
| 311 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32':
10, 'o2Int32': 0 }", formatter.Format(message)); |
| 312 } |
| 313 |
| 314 [Test] |
| 315 public void TimestampStandalone() |
| 316 { |
| 317 Assert.AreEqual("1970-01-01T00:00:00Z", new Timestamp().ToString()); |
| 318 Assert.AreEqual("1970-01-01T00:00:00.100Z", new Timestamp { Nanos =
100000000 }.ToString()); |
| 319 Assert.AreEqual("1970-01-01T00:00:00.120Z", new Timestamp { Nanos =
120000000 }.ToString()); |
| 320 Assert.AreEqual("1970-01-01T00:00:00.123Z", new Timestamp { Nanos =
123000000 }.ToString()); |
| 321 Assert.AreEqual("1970-01-01T00:00:00.123400Z", new Timestamp { Nanos
= 123400000 }.ToString()); |
| 322 Assert.AreEqual("1970-01-01T00:00:00.123450Z", new Timestamp { Nanos
= 123450000 }.ToString()); |
| 323 Assert.AreEqual("1970-01-01T00:00:00.123456Z", new Timestamp { Nanos
= 123456000 }.ToString()); |
| 324 Assert.AreEqual("1970-01-01T00:00:00.123456700Z", new Timestamp { Na
nos = 123456700 }.ToString()); |
| 325 Assert.AreEqual("1970-01-01T00:00:00.123456780Z", new Timestamp { Na
nos = 123456780 }.ToString()); |
| 326 Assert.AreEqual("1970-01-01T00:00:00.123456789Z", new Timestamp { Na
nos = 123456789 }.ToString()); |
| 327 |
| 328 // One before and one after the Unix epoch |
| 329 Assert.AreEqual("1673-06-19T12:34:56Z", |
| 330 new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimest
amp().ToString()); |
| 331 Assert.AreEqual("2015-07-31T10:29:34Z", |
| 332 new DateTime(2015, 7, 31, 10, 29, 34, DateTimeKind.Utc).ToTimest
amp().ToString()); |
| 333 } |
| 334 |
| 335 [Test] |
| 336 public void TimestampField() |
| 337 { |
| 338 var message = new TestWellKnownTypes { TimestampField = new Timestam
p() }; |
| 339 AssertJson("{ 'timestampField': '1970-01-01T00:00:00Z' }", JsonForma
tter.Default.Format(message)); |
| 340 } |
| 341 |
| 342 [Test] |
| 343 [TestCase(0, 0, "0s")] |
| 344 [TestCase(1, 0, "1s")] |
| 345 [TestCase(-1, 0, "-1s")] |
| 346 [TestCase(0, 100000000, "0.100s")] |
| 347 [TestCase(0, 120000000, "0.120s")] |
| 348 [TestCase(0, 123000000, "0.123s")] |
| 349 [TestCase(0, 123400000, "0.123400s")] |
| 350 [TestCase(0, 123450000, "0.123450s")] |
| 351 [TestCase(0, 123456000, "0.123456s")] |
| 352 [TestCase(0, 123456700, "0.123456700s")] |
| 353 [TestCase(0, 123456780, "0.123456780s")] |
| 354 [TestCase(0, 123456789, "0.123456789s")] |
| 355 [TestCase(0, -100000000, "-0.100s")] |
| 356 [TestCase(1, 100000000, "1.100s")] |
| 357 [TestCase(-1, -100000000, "-1.100s")] |
| 358 // Non-normalized examples |
| 359 [TestCase(1, 2123456789, "3.123456789s")] |
| 360 [TestCase(1, -100000000, "0.900s")] |
| 361 public void DurationStandalone(long seconds, int nanoseconds, string exp
ected) |
| 362 { |
| 363 Assert.AreEqual(expected, new Duration { Seconds = seconds, Nanos =
nanoseconds }.ToString()); |
| 364 } |
| 365 |
| 366 [Test] |
| 367 public void DurationField() |
| 368 { |
| 369 var message = new TestWellKnownTypes { DurationField = new Duration(
) }; |
| 370 AssertJson("{ 'durationField': '0s' }", JsonFormatter.Default.Format
(message)); |
| 371 } |
| 372 |
| 373 [Test] |
| 374 public void StructSample() |
| 375 { |
| 376 var message = new Struct |
| 377 { |
| 378 Fields = |
| 379 { |
| 380 { "a", new Value { NullValue = new NullValue() } }, |
| 381 { "b", new Value { BoolValue = false } }, |
| 382 { "c", new Value { NumberValue = 10.5 } }, |
| 383 { "d", new Value { StringValue = "text" } }, |
| 384 { "e", new Value { ListValue = new ListValue { Values = { ne
w Value { StringValue = "t1" }, new Value { NumberValue = 5 } } } } }, |
| 385 { "f", new Value { StructValue = new Struct { Fields = { { "
nested", new Value { StringValue = "value" } } } } } } |
| 386 } |
| 387 }; |
| 388 AssertJson("{ 'a': null, 'b': false, 'c': 10.5, 'd': 'text', 'e': [
't1', 5 ], 'f': { 'nested': 'value' } }", message.ToString()); |
| 389 } |
| 390 |
| 391 [Test] |
| 392 public void FieldMaskStandalone() |
| 393 { |
| 394 var fieldMask = new FieldMask { Paths = { "", "single", "with_unders
core", "nested.field.name", "nested..double_dot" } }; |
| 395 Assert.AreEqual(",single,withUnderscore,nested.field.name,nested..do
ubleDot", fieldMask.ToString()); |
| 396 |
| 397 // Invalid, but we shouldn't create broken JSON... |
| 398 fieldMask = new FieldMask { Paths = { "x\\y" } }; |
| 399 Assert.AreEqual(@"x\\y", fieldMask.ToString()); |
| 400 } |
| 401 |
| 402 [Test] |
| 403 public void FieldMaskField() |
| 404 { |
| 405 var message = new TestWellKnownTypes { FieldMaskField = new FieldMas
k { Paths = { "user.display_name", "photo" } } }; |
| 406 AssertJson("{ 'fieldMaskField': 'user.displayName,photo' }", JsonFor
matter.Default.Format(message)); |
| 407 } |
| 408 |
| 409 /// <summary> |
| 410 /// Checks that the actual JSON is the same as the expected JSON - but a
fter replacing |
| 411 /// all apostrophes in the expected JSON with double quotes. This basica
lly makes the tests easier |
| 412 /// to read. |
| 413 /// </summary> |
| 414 private static void AssertJson(string expectedJsonWithApostrophes, strin
g actualJson) |
| 415 { |
| 416 var expectedJson = expectedJsonWithApostrophes.Replace("'", "\""); |
| 417 Assert.AreEqual(expectedJson, actualJson); |
| 418 } |
| 419 } |
| 420 } |
| OLD | NEW |