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 |