blob: ac33a750f93c6318716d6f202cd51aeff78ccd9f [file] [log] [blame]
Jon Skeetf8c151f2015-07-03 11:56:29 +01001#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
33using System;
Jon Skeetf8c151f2015-07-03 11:56:29 +010034using Google.Protobuf.TestProtos;
35using NUnit.Framework;
Jon Skeet4fed0b52015-07-31 10:33:31 +010036using UnitTest.Issues.TestProtos;
Jon Skeetf8c151f2015-07-03 11:56:29 +010037
38namespace Google.Protobuf
39{
Jon Skeet6cf5f662015-07-31 10:44:59 +010040 /// <summary>
41 /// Tests for the JSON formatter. Note that in these tests, double quotes are replaced with apostrophes
42 /// for the sake of readability (embedding \" everywhere is painful). See the AssertJson method for details.
43 /// </summary>
Jon Skeetf8c151f2015-07-03 11:56:29 +010044 public class JsonFormatterTest
45 {
46 [Test]
47 public void DefaultValues_WhenOmitted()
48 {
49 var formatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: false));
50
Jon Skeet6cf5f662015-07-31 10:44:59 +010051 AssertJson("{ }", formatter.Format(new ForeignMessage()));
52 AssertJson("{ }", formatter.Format(new TestAllTypes()));
53 AssertJson("{ }", formatter.Format(new TestMap()));
Jon Skeetf8c151f2015-07-03 11:56:29 +010054 }
55
56 [Test]
57 public void DefaultValues_WhenIncluded()
58 {
59 var formatter = new JsonFormatter(new JsonFormatter.Settings(formatDefaultValues: true));
Jon Skeet6cf5f662015-07-31 10:44:59 +010060 AssertJson("{ 'c': 0 }", formatter.Format(new ForeignMessage()));
Jon Skeetf8c151f2015-07-03 11:56:29 +010061 }
62
63 [Test]
64 public void AllSingleFields()
65 {
66 var message = new TestAllTypes
67 {
68 SingleBool = true,
69 SingleBytes = ByteString.CopyFrom(1, 2, 3, 4),
70 SingleDouble = 23.5,
71 SingleFixed32 = 23,
72 SingleFixed64 = 1234567890123,
73 SingleFloat = 12.25f,
74 SingleForeignEnum = ForeignEnum.FOREIGN_BAR,
75 SingleForeignMessage = new ForeignMessage { C = 10 },
76 SingleImportEnum = ImportEnum.IMPORT_BAZ,
77 SingleImportMessage = new ImportMessage { D = 20 },
78 SingleInt32 = 100,
79 SingleInt64 = 3210987654321,
80 SingleNestedEnum = TestAllTypes.Types.NestedEnum.FOO,
81 SingleNestedMessage = new TestAllTypes.Types.NestedMessage { Bb = 35 },
82 SinglePublicImportMessage = new PublicImportMessage { E = 54 },
83 SingleSfixed32 = -123,
84 SingleSfixed64 = -12345678901234,
85 SingleSint32 = -456,
86 SingleSint64 = -12345678901235,
87 SingleString = "test\twith\ttabs",
88 SingleUint32 = uint.MaxValue,
89 SingleUint64 = ulong.MaxValue,
90 };
91 var actualText = JsonFormatter.Default.Format(message);
92
Jon Skeet6cf5f662015-07-31 10:44:59 +010093 // Fields in numeric order
Jon Skeetf8c151f2015-07-03 11:56:29 +010094 var expectedText = "{ " +
Jon Skeet6cf5f662015-07-31 10:44:59 +010095 "'singleInt32': 100, " +
96 "'singleInt64': '3210987654321', " +
97 "'singleUint32': 4294967295, " +
98 "'singleUint64': '18446744073709551615', " +
99 "'singleSint32': -456, " +
100 "'singleSint64': '-12345678901235', " +
101 "'singleFixed32': 23, " +
102 "'singleFixed64': '1234567890123', " +
103 "'singleSfixed32': -123, " +
104 "'singleSfixed64': '-12345678901234', " +
105 "'singleFloat': 12.25, " +
106 "'singleDouble': 23.5, " +
107 "'singleBool': true, " +
108 "'singleString': 'test\\twith\\ttabs', " +
109 "'singleBytes': 'AQIDBA==', " +
110 "'singleNestedMessage': { 'bb': 35 }, " +
111 "'singleForeignMessage': { 'c': 10 }, " +
112 "'singleImportMessage': { 'd': 20 }, " +
113 "'singleNestedEnum': 'FOO', " +
114 "'singleForeignEnum': 'FOREIGN_BAR', " +
115 "'singleImportEnum': 'IMPORT_BAZ', " +
116 "'singlePublicImportMessage': { 'e': 54 }" +
Jon Skeetf8c151f2015-07-03 11:56:29 +0100117 " }";
Jon Skeet6cf5f662015-07-31 10:44:59 +0100118 AssertJson(expectedText, actualText);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100119 }
120
121 [Test]
122 public void RepeatedField()
123 {
Jon Skeet6cf5f662015-07-31 10:44:59 +0100124 AssertJson("{ 'repeatedInt32': [ 1, 2, 3, 4, 5 ] }",
Jon Skeetf8c151f2015-07-03 11:56:29 +0100125 JsonFormatter.Default.Format(new TestAllTypes { RepeatedInt32 = { 1, 2, 3, 4, 5 } }));
126 }
127
128 [Test]
129 public void MapField_StringString()
130 {
Jon Skeet6cf5f662015-07-31 10:44:59 +0100131 AssertJson("{ 'mapStringString': { 'with spaces': 'bar', 'a': 'b' } }",
Jon Skeetf8c151f2015-07-03 11:56:29 +0100132 JsonFormatter.Default.Format(new TestMap { MapStringString = { { "with spaces", "bar" }, { "a", "b" } } }));
133 }
134
135 [Test]
136 public void MapField_Int32Int32()
137 {
138 // The keys are quoted, but the values aren't.
Jon Skeet6cf5f662015-07-31 10:44:59 +0100139 AssertJson("{ 'mapInt32Int32': { '0': 1, '2': 3 } }",
Jon Skeetf8c151f2015-07-03 11:56:29 +0100140 JsonFormatter.Default.Format(new TestMap { MapInt32Int32 = { { 0, 1 }, { 2, 3 } } }));
141 }
142
143 [Test]
144 public void MapField_BoolBool()
145 {
146 // The keys are quoted, but the values aren't.
Jon Skeet6cf5f662015-07-31 10:44:59 +0100147 AssertJson("{ 'mapBoolBool': { 'false': true, 'true': false } }",
Jon Skeetf8c151f2015-07-03 11:56:29 +0100148 JsonFormatter.Default.Format(new TestMap { MapBoolBool = { { false, true }, { true, false } } }));
149 }
150
151 [TestCase(1.0, "1")]
Jon Skeet6cf5f662015-07-31 10:44:59 +0100152 [TestCase(double.NaN, "'NaN'")]
153 [TestCase(double.PositiveInfinity, "'Infinity'")]
154 [TestCase(double.NegativeInfinity, "'-Infinity'")]
Jon Skeetf8c151f2015-07-03 11:56:29 +0100155 public void DoubleRepresentations(double value, string expectedValueText)
156 {
157 var message = new TestAllTypes { SingleDouble = value };
158 string actualText = JsonFormatter.Default.Format(message);
Jon Skeet6cf5f662015-07-31 10:44:59 +0100159 string expectedText = "{ 'singleDouble': " + expectedValueText + " }";
160 AssertJson(expectedText, actualText);
Jon Skeetf8c151f2015-07-03 11:56:29 +0100161 }
162
163 [Test]
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100164 public void UnknownEnumValueOmitted_SingleField()
Jon Skeetf8c151f2015-07-03 11:56:29 +0100165 {
166 var message = new TestAllTypes { SingleForeignEnum = (ForeignEnum) 100 };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100167 AssertJson("{ }", JsonFormatter.Default.Format(message));
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100168 }
169
170 [Test]
171 public void UnknownEnumValueOmitted_RepeatedField()
172 {
173 var message = new TestAllTypes { RepeatedForeignEnum = { ForeignEnum.FOREIGN_BAZ, (ForeignEnum) 100, ForeignEnum.FOREIGN_FOO } };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100174 AssertJson("{ 'repeatedForeignEnum': [ 'FOREIGN_BAZ', 'FOREIGN_FOO' ] }", JsonFormatter.Default.Format(message));
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100175 }
176
177 [Test]
178 public void UnknownEnumValueOmitted_MapField()
179 {
180 // This matches the C++ behaviour.
181 var message = new TestMap { MapInt32Enum = { { 1, MapEnum.MAP_ENUM_FOO }, { 2, (MapEnum) 100 }, { 3, MapEnum.MAP_ENUM_BAR } } };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100182 AssertJson("{ 'mapInt32Enum': { '1': 'MAP_ENUM_FOO', '3': 'MAP_ENUM_BAR' } }", JsonFormatter.Default.Format(message));
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100183 }
184
185 [Test]
186 public void UnknownEnumValueOmitted_RepeatedField_AllEntriesUnknown()
187 {
188 // *Maybe* we should hold off on writing the "[" until we find that we've got at least one value to write...
189 // but this is what happens at the moment, and it doesn't seem too awful.
190 var message = new TestAllTypes { RepeatedForeignEnum = { (ForeignEnum) 200, (ForeignEnum) 100 } };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100191 AssertJson("{ 'repeatedForeignEnum': [ ] }", JsonFormatter.Default.Format(message));
Jon Skeetf8c151f2015-07-03 11:56:29 +0100192 }
193
194 [Test]
195 public void NullValueForMessage()
196 {
197 var message = new TestMap { MapInt32ForeignMessage = { { 10, null } } };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100198 AssertJson("{ 'mapInt32ForeignMessage': { '10': null } }", JsonFormatter.Default.Format(message));
Jon Skeetf8c151f2015-07-03 11:56:29 +0100199 }
200
201 [Test]
202 [TestCase("a\u17b4b", "a\\u17b4b")] // Explicit
203 [TestCase("a\u0601b", "a\\u0601b")] // Ranged
204 [TestCase("a\u0605b", "a\u0605b")] // Passthrough (note lack of double backslash...)
205 public void SimpleNonAscii(string text, string encoded)
206 {
207 var message = new TestAllTypes { SingleString = text };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100208 AssertJson("{ 'singleString': '" + encoded + "' }", JsonFormatter.Default.Format(message));
Jon Skeetf8c151f2015-07-03 11:56:29 +0100209 }
210
211 [Test]
212 public void SurrogatePairEscaping()
213 {
214 var message = new TestAllTypes { SingleString = "a\uD801\uDC01b" };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100215 AssertJson("{ 'singleString': 'a\\ud801\\udc01b' }", JsonFormatter.Default.Format(message));
Jon Skeetf8c151f2015-07-03 11:56:29 +0100216 }
217
218 [Test]
219 public void InvalidSurrogatePairsFail()
220 {
221 // Note: don't use TestCase for these, as the strings can't be reliably represented
222 // See http://codeblog.jonskeet.uk/2014/11/07/when-is-a-string-not-a-string/
223
224 // Lone low surrogate
225 var message = new TestAllTypes { SingleString = "a\uDC01b" };
226 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message));
227
228 // Lone high surrogate
229 message = new TestAllTypes { SingleString = "a\uD801b" };
230 Assert.Throws<ArgumentException>(() => JsonFormatter.Default.Format(message));
231 }
232
233 [Test]
234 [TestCase("foo_bar", "fooBar")]
235 [TestCase("bananaBanana", "bananaBanana")]
236 [TestCase("BANANABanana", "bananaBanana")]
237 public void ToCamelCase(string original, string expected)
238 {
239 Assert.AreEqual(expected, JsonFormatter.ToCamelCase(original));
240 }
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100241
242 [Test]
243 [TestCase(null, "{ }")]
Jon Skeet6cf5f662015-07-31 10:44:59 +0100244 [TestCase("x", "{ 'fooString': 'x' }")]
245 [TestCase("", "{ 'fooString': '' }")]
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100246 [TestCase(null, "{ }")]
247 public void Oneof(string fooStringValue, string expectedJson)
248 {
249 var message = new TestOneof();
250 if (fooStringValue != null)
251 {
252 message.FooString = fooStringValue;
253 }
254
255 // We should get the same result both with and without "format default values".
256 var formatter = new JsonFormatter(new JsonFormatter.Settings(false));
Jon Skeet6cf5f662015-07-31 10:44:59 +0100257 AssertJson(expectedJson, formatter.Format(message));
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100258 formatter = new JsonFormatter(new JsonFormatter.Settings(true));
Jon Skeet6cf5f662015-07-31 10:44:59 +0100259 AssertJson(expectedJson, formatter.Format(message));
Jon Skeet6ea9bc72015-07-10 14:05:52 +0100260 }
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100261
262 [Test]
263 public void WrapperFormatting_Single()
264 {
265 // Just a few examples, handling both classes and value types, and
266 // default vs non-default values
267 var message = new TestWellKnownTypes
268 {
269 Int64Field = 10,
270 Int32Field = 0,
271 BytesField = ByteString.FromBase64("ABCD"),
272 StringField = ""
273 };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100274 var expectedJson = "{ 'int64Field': '10', 'int32Field': 0, 'stringField': '', 'bytesField': 'ABCD' }";
275 AssertJson(expectedJson, JsonFormatter.Default.Format(message));
Jon Skeetc9fd53a2015-07-20 11:48:24 +0100276 }
277
278 [Test]
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 }
Jon Skeet4fed0b52015-07-31 10:33:31 +0100288
289 [Test]
290 public void OutputIsInNumericFieldOrder_NoDefaults()
291 {
292 var formatter = new JsonFormatter(new JsonFormatter.Settings(false));
293 var message = new TestJsonFieldOrdering { PlainString = "p1", PlainInt32 = 2 };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100294 AssertJson("{ 'plainString': 'p1', 'plainInt32': 2 }", formatter.Format(message));
Jon Skeet4fed0b52015-07-31 10:33:31 +0100295 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100296 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message));
Jon Skeet4fed0b52015-07-31 10:33:31 +0100297 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100298 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message));
Jon Skeet4fed0b52015-07-31 10:33:31 +0100299 }
300
301 [Test]
302 public void OutputIsInNumericFieldOrder_WithDefaults()
303 {
304 var formatter = new JsonFormatter(new JsonFormatter.Settings(true));
305 var message = new TestJsonFieldOrdering();
Jon Skeet6cf5f662015-07-31 10:44:59 +0100306 AssertJson("{ 'plainString': '', 'plainInt32': 0 }", formatter.Format(message));
Jon Skeet4fed0b52015-07-31 10:33:31 +0100307 message = new TestJsonFieldOrdering { O1Int32 = 5, O2String = "o2", PlainInt32 = 10, PlainString = "plain" };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100308 AssertJson("{ 'plainString': 'plain', 'o2String': 'o2', 'plainInt32': 10, 'o1Int32': 5 }", formatter.Format(message));
Jon Skeet4fed0b52015-07-31 10:33:31 +0100309 message = new TestJsonFieldOrdering { O1String = "", O2Int32 = 0, PlainInt32 = 10, PlainString = "plain" };
Jon Skeet6cf5f662015-07-31 10:44:59 +0100310 AssertJson("{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }", formatter.Format(message));
311 }
312
313 /// <summary>
314 /// Checks that the actual JSON is the same as the expected JSON - but after replacing
315 /// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
316 /// to read.
317 /// </summary>
318 private static void AssertJson(string expectedJsonWithApostrophes, string actualJson)
319 {
320 var expectedJson = expectedJsonWithApostrophes.Replace("'", "\"");
321 Assert.AreEqual(expectedJson, actualJson);
Jon Skeet4fed0b52015-07-31 10:33:31 +0100322 }
Jon Skeetf8c151f2015-07-03 11:56:29 +0100323 }
324}