• 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 //
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