• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 using Google.Protobuf.Reflection;
39 
40 using static Google.Protobuf.JsonParserTest; // For WrapInQuotes
41 
42 namespace Google.Protobuf
43 {
44     /// <summary>
45     /// Tests for the JSON formatter. Note that in these tests, double quotes are replaced with apostrophes
46     /// for the sake of readability (embedding \" everywhere is painful). See the AssertJson method for details.
47     /// </summary>
48     public class JsonFormatterTest
49     {
50         [Test]
DefaultValues_WhenOmitted()51         public void DefaultValues_WhenOmitted()
52         {
53             var formatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: false));
54 
55             AssertJson("{ }", formatter.Format(new ForeignMessage()));
56             AssertJson("{ }", formatter.Format(new TestAllTypes()));
57             AssertJson("{ }", formatter.Format(new TestMap()));
58         }
59 
60         [Test]
DefaultValues_WhenIncluded()61         public void DefaultValues_WhenIncluded()
62         {
63             var formatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: true));
64             AssertJson("{ 'c': 0 }", formatter.Format(new ForeignMessage()));
65         }
66 
67         [Test]
AllSingleFields()68         public void AllSingleFields()
69         {
70             var message = new TestAllTypes
71             {
72                 SingleBool = true,
73                 SingleBytes = ByteString.CopyFrom(1, 2, 3, 4),
74                 SingleDouble = 23.5,
75                 SingleFixed32 = 23,
76                 SingleFixed64 = 1234567890123,
77                 SingleFloat = 12.25f,
78                 SingleForeignEnum = ForeignEnum.ForeignBar,
79                 SingleForeignMessage = new ForeignMessage { C = 10 },
80                 SingleImportEnum = ImportEnum.ImportBaz,
81                 SingleImportMessage = new ImportMessage { D = 20 },
82                 SingleInt32 = 100,
83                 SingleInt64 = 3210987654321,
84                 SingleNestedEnum = TestAllTypes.Types.NestedEnum.Foo,
85                 SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
86                 SinglePublicImportMessage = new PublicImportMessage { E = 54 },
87                 SingleSfixed32 = -123,
88                 SingleSfixed64 = -12345678901234,
89                 SingleSint32 = -456,
90                 SingleSint64 = -12345678901235,
91                 SingleString = "test\twith\ttabs",
92                 SingleUint32 = uint.MaxValue,
93                 SingleUint64 = ulong.MaxValue,
94             };
95             var actualText = JsonFormatter.Default.Format(message);
96 
97             // Fields in numeric order
98             var expectedText = "{ " +
99                 "'singleInt32': 100, " +
100                 "'singleInt64': '3210987654321', " +
101                 "'singleUint32': 4294967295, " +
102                 "'singleUint64': '18446744073709551615', " +
103                 "'singleSint32': -456, " +
104                 "'singleSint64': '-12345678901235', " +
105                 "'singleFixed32': 23, " +
106                 "'singleFixed64': '1234567890123', " +
107                 "'singleSfixed32': -123, " +
108                 "'singleSfixed64': '-12345678901234', " +
109                 "'singleFloat': 12.25, " +
110                 "'singleDouble': 23.5, " +
111                 "'singleBool': true, " +
112                 "'singleString': 'test\\twith\\ttabs', " +
113                 "'singleBytes': 'AQIDBA==', " +
114                 "'singleNestedMessage': { 'bb': 35 }, " +
115                 "'singleForeignMessage': { 'c': 10 }, " +
116                 "'singleImportMessage': { 'd': 20 }, " +
117                 "'singleNestedEnum': 'FOO', " +
118                 "'singleForeignEnum': 'FOREIGN_BAR', " +
119                 "'singleImportEnum': 'IMPORT_BAZ', " +
120                 "'singlePublicImportMessage': { 'e': 54 }" +
121                 " }";
122             AssertJson(expectedText, actualText);
123         }
124 
125         [Test]
RepeatedField()126         public void RepeatedField()
127         {
128             AssertJson("{ 'repeatedInt32': [ 1, 2, 3, 4, 5 ] }",
129                 JsonFormatter.Default.Format(new TestAllTypes { RepeatedInt32 = { 1, 2, 3, 4, 5 } }));
130         }
131 
132         [Test]
MapField_StringString()133         public void MapField_StringString()
134         {
135             AssertJson("{ 'mapStringString': { 'with spaces': 'bar', 'a': 'b' } }",
136                 JsonFormatter.Default.Format(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } }));
137         }
138 
139         [Test]
MapField_Int32Int32()140         public void MapField_Int32Int32()
141         {
142             // The keys are quoted, but the values aren't.
143             AssertJson("{ 'mapInt32Int32': { '0': 1, '2': 3 } }",
144                 JsonFormatter.Default.Format(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } }));
145         }
146 
147         [Test]
MapField_BoolBool()148         public void MapField_BoolBool()
149         {
150             // The keys are quoted, but the values aren't.
151             AssertJson("{ 'mapBoolBool': { 'false': true, 'true': false } }",
152                 JsonFormatter.Default.Format(new TestMap { MapBoolBool = { { false, true }, { true, false } } }));
153         }
154 
155         [TestCase(1.0, "1")]
156         [TestCase(double.NaN, "'NaN'")]
157         [TestCase(double.PositiveInfinity, "'Infinity'")]
158         [TestCase(double.NegativeInfinity, "'-Infinity'")]
DoubleRepresentations(double value, string expectedValueText)159         public void DoubleRepresentations(double value, string expectedValueText)
160         {
161             var message = new TestAllTypes { SingleDouble = value };
162             string actualText = JsonFormatter.Default.Format(message);
163             string expectedText = "{ 'singleDouble': " + expectedValueText + " }";
164             AssertJson(expectedText, actualText);
165         }
166 
167         [Test]
UnknownEnumValueNumeric_SingleField()168         public void UnknownEnumValueNumeric_SingleField()
169         {
170             var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 100 };
171             AssertJson("{ 'singleForeignEnum': 100 }", JsonFormatter.Default.Format(message));
172         }
173 
174         [Test]
UnknownEnumValueNumeric_RepeatedField()175         public void UnknownEnumValueNumeric_RepeatedField()
176         {
177             var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.ForeignBaz, (ForeignEnum) 100, ForeignEnum.ForeignFoo } };
178             AssertJson("{ 'repeatedForeignEnum': [ 'FOREIGN_BAZ', 100, 'FOREIGN_FOO' ] }", JsonFormatter.Default.Format(message));
179         }
180 
181         [Test]
UnknownEnumValueNumeric_MapField()182         public void UnknownEnumValueNumeric_MapField()
183         {
184             var message = new TestMap { MapInt32Enum = { { 1, MapEnum.Foo }, { 2, (MapEnum) 100 }, { 3, MapEnum.Bar } } };
185             AssertJson("{ 'mapInt32Enum': { '1': 'MAP_ENUM_FOO', '2': 100, '3': 'MAP_ENUM_BAR' } }", JsonFormatter.Default.Format(message));
186         }
187 
188         [Test]
UnknownEnumValue_RepeatedField_AllEntriesUnknown()189         public void UnknownEnumValue_RepeatedField_AllEntriesUnknown()
190         {
191             var message = new TestAllTypes { RepeatedForeignEnum = { (ForeignEnum) 200, (ForeignEnum) 100 } };
192             AssertJson("{ 'repeatedForeignEnum': [ 200, 100 ] }", JsonFormatter.Default.Format(message));
193         }
194 
195         [Test]
196         [TestCase("a\u17b4b", "a\\u17b4b")] // Explicit
197         [TestCase("a\u0601b", "a\\u0601b")] // Ranged
198         [TestCase("a\u0605b", "a\u0605b")] // Passthrough (note lack of double backslash...)
SimpleNonAscii(string text, string encoded)199         public void SimpleNonAscii(string text, string encoded)
200         {
201             var message = new TestAllTypes { SingleString = text };
202             AssertJson("{ 'singleString': '" + encoded + "' }", JsonFormatter.Default.Format(message));
203         }
204 
205         [Test]
SurrogatePairEscaping()206         public void SurrogatePairEscaping()
207         {
208             var message = new TestAllTypes { SingleString = "a\uD801\uDC01b" };
209             AssertJson("{ 'singleString': 'a\\ud801\\udc01b' }", JsonFormatter.Default.Format(message));
210         }
211 
212         [Test]
InvalidSurrogatePairsFail()213         public void InvalidSurrogatePairsFail()
214         {
215             // Note: don't use TestCase for these, as the strings can't be reliably represented
216             // See http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/
217 
218             // Lone low surrogate
219             var message = new TestAllTypes { SingleString = "a\uDC01b" };
220             Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message));
221 
222             // Lone high surrogate
223             message = new TestAllTypes { SingleString = "a\uD801b" };
224             Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message));
225         }
226 
227         [Test]
228         [TestCase("foo_bar", "fooBar")]
229         [TestCase("bananaBanana", "bananaBanana")]
230         [TestCase("BANANABanana", "bananaBanana")]
ToCamelCase(string original, string expected)231         public void ToCamelCase(string original, string expected)
232         {
233             Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original));
234         }
235 
236         [Test]
237         [TestCase(null, "{ }")]
238         [TestCase("x", "{ 'fooString': 'x' }")]
239         [TestCase("", "{ 'fooString': '' }")]
Oneof(string fooStringValue, string expectedJson)240         public void Oneof(string fooStringValue, string expectedJson)
241         {
242             var message = new TestOneof();
243             if (fooStringValue != null)
244             {
245                 message.FooString = fooStringValue;
246             }
247 
248             // We should get the same result both with and without "format default values".
249             var formatter = new JsonFormatter(new JsonFormatter.Settings(false));
250             AssertJson(expectedJson, formatter.Format(message));
251             formatter = new JsonFormatter(new JsonFormatter.Settings(true));
252             AssertJson(expectedJson, formatter.Format(message));
253         }
254 
255         [Test]
WrapperFormatting_Single()256         public void WrapperFormatting_Single()
257         {
258             // Just a few examples, handling both classes and value types, and
259             // default vs non-default values
260             var message = new TestWellKnownTypes
261             {
262                 Int64Field = 10,
263                 Int32Field = 0,
264                 BytesField = ByteString.FromBase64("ABCD"),
265                 StringField = ""
266             };
267             var expectedJson = "{ 'int64Field': '10', 'int32Field': 0, 'stringField': '', 'bytesField': 'ABCD' }";
268             AssertJson(expectedJson, JsonFormatter.Default.Format(message));
269         }
270 
271         [Test]
WrapperFormatting_Message()272         public void WrapperFormatting_Message()
273         {
274             Assert.AreEqual("\"\"", JsonFormatter.Default.Format(new StringValue()));
275             Assert.AreEqual("0", JsonFormatter.Default.Format(new Int32Value()));
276         }
277 
278         [Test]
WrapperFormatting_IncludeNull()279         public void WrapperFormatting_IncludeNull()
280         {
281             // The actual JSON here is very large because there are lots of fields. Just test a couple of them.
282             var message = new TestWellKnownTypes { Int32Field = 10 };
283             var formatter = new JsonFormatter(new JsonFormatter.Settings(true));
284             var actualJson = formatter.Format(message);
285             Assert.IsTrue(actualJson.Contains("\"int64Field\": null"));
286             Assert.IsFalse(actualJson.Contains("\"int32Field\": null"));
287         }
288 
289         [Test]
OutputIsInNumericFieldOrder_NoDefaults()290         public void OutputIsInNumericFieldOrder_NoDefaults()
291         {
292             var formatter = new JsonFormatter(new JsonFormatter.Settings(false));
293             var message = new TestJsonFieldOrdering { PlainString = "p1", PlainInt32 = 2 };
294             AssertJson("{ 'plainString': 'p1', 'plainInt32': 2 }", formatter.Format(message));
295             message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" };
296             AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message));
297             message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" };
298             AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message));
299         }
300 
301         [Test]
OutputIsInNumericFieldOrder_WithDefaults()302         public void OutputIsInNumericFieldOrder_WithDefaults()
303         {
304             var formatter = new JsonFormatter(new JsonFormatter.Settings(true));
305             var message = new TestJsonFieldOrdering();
306             AssertJson("{ 'plainString': '', 'plainInt32': 0 }", formatter.Format(message));
307             message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" };
308             AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message));
309             message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" };
310             AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message));
311         }
312 
313         [Test]
314         [TestCase("1970-01-01T00:00:00Z", 0)]
315         [TestCase("1970-01-01T00:00:00.000000001Z", 1)]
316         [TestCase("1970-01-01T00:00:00.000000010Z", 10)]
317         [TestCase("1970-01-01T00:00:00.000000100Z", 100)]
318         [TestCase("1970-01-01T00:00:00.000001Z", 1000)]
319         [TestCase("1970-01-01T00:00:00.000010Z", 10000)]
320         [TestCase("1970-01-01T00:00:00.000100Z", 100000)]
321         [TestCase("1970-01-01T00:00:00.001Z", 1000000)]
322         [TestCase("1970-01-01T00:00:00.010Z", 10000000)]
323         [TestCase("1970-01-01T00:00:00.100Z", 100000000)]
324         [TestCase("1970-01-01T00:00:00.120Z", 120000000)]
325         [TestCase("1970-01-01T00:00:00.123Z", 123000000)]
326         [TestCase("1970-01-01T00:00:00.123400Z", 123400000)]
327         [TestCase("1970-01-01T00:00:00.123450Z", 123450000)]
328         [TestCase("1970-01-01T00:00:00.123456Z", 123456000)]
329         [TestCase("1970-01-01T00:00:00.123456700Z", 123456700)]
330         [TestCase("1970-01-01T00:00:00.123456780Z", 123456780)]
331         [TestCase("1970-01-01T00:00:00.123456789Z", 123456789)]
TimestampStandalone(string expected, int nanos)332         public void TimestampStandalone(string expected, int nanos)
333         {
334             Assert.AreEqual(WrapInQuotes(expected), new Timestamp { Nanos = nanos }.ToString());
335         }
336 
337         [Test]
TimestampStandalone_FromDateTime()338         public void TimestampStandalone_FromDateTime()
339         {
340             // One before and one after the Unix epoch, more easily represented via DateTime.
341             Assert.AreEqual("\"1673-06-19T12:34:56Z\"",
342                 new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp().ToString());
343             Assert.AreEqual("\"2015-07-31T10:29:34Z\"",
344                 new DateTime(2015, 7, 31, 10, 29, 34, DateTimeKind.Utc).ToTimestamp().ToString());
345         }
346 
347         [Test]
348         [TestCase(-1, -1)] // Would be valid as duration
349         [TestCase(1, Timestamp.MaxNanos + 1)]
350         [TestCase(Timestamp.UnixSecondsAtBclMaxValue + 1, 0)]
351         [TestCase(Timestamp.UnixSecondsAtBclMinValue - 1, 0)]
TimestampStandalone_NonNormalized(long seconds, int nanoseconds)352         public void TimestampStandalone_NonNormalized(long seconds, int nanoseconds)
353         {
354             var timestamp = new Timestamp { Seconds = seconds, Nanos = nanoseconds };
355             Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(timestamp));
356         }
357 
358         [Test]
TimestampField()359         public void TimestampField()
360         {
361             var message = new TestWellKnownTypes { TimestampField = new Timestamp() };
362             AssertJson("{ 'timestampField': '1970-01-01T00:00:00Z' }", JsonFormatter.Default.Format(message));
363         }
364 
365         [Test]
366         [TestCase(0, 0, "0s")]
367         [TestCase(1, 0, "1s")]
368         [TestCase(-1, 0, "-1s")]
369         [TestCase(0, 1, "0.000000001s")]
370         [TestCase(0, 10, "0.000000010s")]
371         [TestCase(0, 100, "0.000000100s")]
372         [TestCase(0, 1000, "0.000001s")]
373         [TestCase(0, 10000, "0.000010s")]
374         [TestCase(0, 100000, "0.000100s")]
375         [TestCase(0, 1000000, "0.001s")]
376         [TestCase(0, 10000000, "0.010s")]
377         [TestCase(0, 100000000, "0.100s")]
378         [TestCase(0, 120000000, "0.120s")]
379         [TestCase(0, 123000000, "0.123s")]
380         [TestCase(0, 123400000, "0.123400s")]
381         [TestCase(0, 123450000, "0.123450s")]
382         [TestCase(0, 123456000, "0.123456s")]
383         [TestCase(0, 123456700, "0.123456700s")]
384         [TestCase(0, 123456780, "0.123456780s")]
385         [TestCase(0, 123456789, "0.123456789s")]
386         [TestCase(0, -100000000, "-0.100s")]
387         [TestCase(1, 100000000, "1.100s")]
388         [TestCase(-1, -100000000, "-1.100s")]
DurationStandalone(long seconds, int nanoseconds, string expected)389         public void DurationStandalone(long seconds, int nanoseconds, string expected)
390         {
391             var json = JsonFormatter.Default.Format(new Duration { Seconds = seconds, Nanos = nanoseconds });
392             Assert.AreEqual(WrapInQuotes(expected), json);
393         }
394 
395         [Test]
396         [TestCase(1, 2123456789)]
397         [TestCase(1, -100000000)]
DurationStandalone_NonNormalized(long seconds, int nanoseconds)398         public void DurationStandalone_NonNormalized(long seconds, int nanoseconds)
399         {
400             var duration = new Duration { Seconds = seconds, Nanos = nanoseconds };
401             Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(duration));
402         }
403 
404         [Test]
DurationField()405         public void DurationField()
406         {
407             var message = new TestWellKnownTypes { DurationField = new Duration() };
408             AssertJson("{ 'durationField': '0s' }", JsonFormatter.Default.Format(message));
409         }
410 
411         [Test]
StructSample()412         public void StructSample()
413         {
414             var message = new Struct
415             {
416                 Fields =
417                 {
418                     { "a", Value.ForNull() },
419                     { "b", Value.ForBool(false) },
420                     { "c", Value.ForNumber(10.5) },
421                     { "d", Value.ForString("text") },
422                     { "e", Value.ForList(Value.ForString("t1"), Value.ForNumber(5)) },
423                     { "f", Value.ForStruct(new Struct { Fields = { { "nested", Value.ForString("value") } } }) }
424                 }
425             };
426             AssertJson("{ 'a': null, 'b': false, 'c': 10.5, 'd': 'text', 'e': [ 't1', 5 ], 'f': { 'nested': 'value' } }", message.ToString());
427         }
428 
429         [Test]
430         [TestCase("foo__bar")]
431         [TestCase("foo_3_ar")]
432         [TestCase("fooBar")]
FieldMaskInvalid(string input)433         public void FieldMaskInvalid(string input)
434         {
435             var mask = new FieldMask { Paths = { input } };
436             Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(mask));
437         }
438 
439         [Test]
FieldMaskStandalone()440         public void FieldMaskStandalone()
441         {
442             var fieldMask = new FieldMask { Paths = { "", "single", "with_underscore", "nested.field.name", "nested..double_dot" } };
443             Assert.AreEqual("\",single,withUnderscore,nested.field.name,nested..doubleDot\"", fieldMask.ToString());
444 
445             // Invalid, but we shouldn't create broken JSON...
446             fieldMask = new FieldMask { Paths = { "x\\y" } };
447             Assert.AreEqual(@"""x\\y""", fieldMask.ToString());
448         }
449 
450         [Test]
FieldMaskField()451         public void FieldMaskField()
452         {
453             var message = new TestWellKnownTypes { FieldMaskField = new FieldMask { Paths = { "user.display_name", "photo" } } };
454             AssertJson("{ 'fieldMaskField': 'user.displayName,photo' }", JsonFormatter.Default.Format(message));
455         }
456 
457         // SourceContext is an example of a well-known type with no special JSON handling
458         [Test]
SourceContextStandalone()459         public void SourceContextStandalone()
460         {
461             var message = new SourceContext { FileName = "foo.proto" };
462             AssertJson("{ 'fileName': 'foo.proto' }", JsonFormatter.Default.Format(message));
463         }
464 
465         [Test]
AnyWellKnownType()466         public void AnyWellKnownType()
467         {
468             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, TypeRegistry.FromMessages(Timestamp.Descriptor)));
469             var timestamp = new DateTime(1673, 6, 19, 12, 34, 56, DateTimeKind.Utc).ToTimestamp();
470             var any = Any.Pack(timestamp);
471             AssertJson("{ '@type': 'type.googleapis.com/google.protobuf.Timestamp', 'value': '1673-06-19T12:34:56Z' }", formatter.Format(any));
472         }
473 
474         [Test]
AnyMessageType()475         public void AnyMessageType()
476         {
477             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, TypeRegistry.FromMessages(TestAllTypes.Descriptor)));
478             var message = new TestAllTypes { SingleInt32 = 10, SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 20 } };
479             var any = Any.Pack(message);
480             AssertJson("{ '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes', 'singleInt32': 10, 'singleNestedMessage': { 'bb': 20 } }", formatter.Format(any));
481         }
482 
483         [Test]
AnyMessageType_CustomPrefix()484         public void AnyMessageType_CustomPrefix()
485         {
486             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, TypeRegistry.FromMessages(TestAllTypes.Descriptor)));
487             var message = new TestAllTypes { SingleInt32 = 10 };
488             var any = Any.Pack(message, "foo.bar/baz");
489             AssertJson("{ '@type': 'foo.bar/baz/protobuf_unittest.TestAllTypes', 'singleInt32': 10 }", formatter.Format(any));
490         }
491 
492         [Test]
AnyNested()493         public void AnyNested()
494         {
495             var registry = TypeRegistry.FromMessages(TestWellKnownTypes.Descriptor, TestAllTypes.Descriptor);
496             var formatter = new JsonFormatter(new JsonFormatter.Settings(false, registry));
497 
498             // Nest an Any as the value of an Any.
499             var doubleNestedMessage = new TestAllTypes { SingleInt32 = 20 };
500             var nestedMessage = Any.Pack(doubleNestedMessage);
501             var message = new TestWellKnownTypes { AnyField = Any.Pack(nestedMessage) };
502             AssertJson("{ 'anyField': { '@type': 'type.googleapis.com/google.protobuf.Any', 'value': { '@type': 'type.googleapis.com/protobuf_unittest.TestAllTypes', 'singleInt32': 20 } } }",
503                 formatter.Format(message));
504         }
505 
506         [Test]
AnyUnknownType()507         public void AnyUnknownType()
508         {
509             // The default type registry doesn't have any types in it.
510             var message = new TestAllTypes();
511             var any = Any.Pack(message);
512             Assert.Throws<InvalidOperationException>(() => JsonFormatter.Default.Format(any));
513         }
514 
515         /// <summary>
516         /// Checks that the actual JSON is the same as the expected JSON - but after replacing
517         /// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
518         /// to read.
519         /// </summary>
AssertJson(string expectedJsonWithApostrophes, string actualJson)520         private static void AssertJson(string expectedJsonWithApostrophes, string actualJson)
521         {
522             var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"");
523             Assert.AreEqual(expectedJson, actualJson);
524         }
525     }
526 }
527