Skip to content

Commit 8255e36

Browse files
committed
Fixed JSON encoding of control characters
- Fixed JSON encoding of control characters (fixes #72) - Added test case for converting derived DTO classes (see #70)
1 parent dd79671 commit 8255e36

File tree

4 files changed

+184
-51
lines changed

4 files changed

+184
-51
lines changed

cppwamp/include/cppwamp/internal/json.ipp

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,49 +117,72 @@ struct EncodeJson : public Visitor<>
117117

118118
void operator()(const std::string& s, TBuffer& buf) const
119119
{
120-
static const std::string quote("\"");
121-
write(buf, quote);
122-
write(buf, s);
123-
write(buf, quote);
120+
writeChar(buf, '\"');
121+
for (char c: s)
122+
writeEncodedChar(buf, c);
123+
writeChar(buf, '\"');
124124
}
125125

126126
void operator()(const Array& a, TBuffer& buf) const
127127
{
128-
static const std::string openBracket("[");
129-
static const std::string closeBracket("]");
130-
static const std::string comma(",");
131-
write(buf, openBracket);
128+
writeChar(buf, '[');
132129
for (const auto& v: a)
133130
{
134131
if (&v != &a.front())
135-
write(buf, comma);
132+
writeChar(buf, ',');
136133
applyWithOperand(*this, v, buf);
137134
}
138-
write(buf, closeBracket);
135+
writeChar(buf, ']');
139136
}
140137

141138
void operator()(const Object& o, TBuffer& buf) const
142139
{
143-
static const std::string openBrace("{");
144-
static const std::string closeBrace("}");
145-
static const std::string colon(":");
146-
static const std::string comma(",");
147-
write(buf, openBrace);
140+
writeChar(buf, '{');
148141
for (auto kv = o.cbegin(); kv != o.cend(); ++kv)
149142
{
150143
if (kv != o.cbegin())
151-
write(buf, comma);
144+
writeChar(buf, ',');
152145
this->operator()(kv->first, buf);
153-
write(buf, colon);
146+
writeChar(buf, ':');
154147
applyWithOperand(*this, kv->second, buf);
155148
}
156-
write(buf, closeBrace);
149+
writeChar(buf, '}');
157150
}
158151

159152
static void write(TBuffer& buf, const std::string& s)
160153
{
161154
buf.write(s.data(), s.length());
162155
}
156+
157+
static void writeChar(TBuffer& buf, char c)
158+
{
159+
buf.write(&c, 1);
160+
}
161+
162+
static void writeEncodedChar(TBuffer& buf, char c)
163+
{
164+
switch (c)
165+
{
166+
case '\"': writeChar(buf, '\\'); writeChar(buf, '\"'); return;
167+
case '\\': writeChar(buf, '\\'); writeChar(buf, '\\'); return;
168+
case '\b': writeChar(buf, '\\'); writeChar(buf, 'b'); return;
169+
case '\f': writeChar(buf, '\\'); writeChar(buf, 'f'); return;
170+
case '\n': writeChar(buf, '\\'); writeChar(buf, 'n'); return;
171+
case '\r': writeChar(buf, '\\'); writeChar(buf, 'r'); return;
172+
case '\t': writeChar(buf, '\\'); writeChar(buf, 't'); return;
173+
}
174+
175+
if (c <= 0x1f)
176+
{
177+
auto n = static_cast<unsigned int>(c);
178+
char str[8];
179+
auto length = std::snprintf(str, sizeof(str), "\\u%04X", n);
180+
assert(length < sizeof(str));
181+
buf.write(str, length);
182+
}
183+
else
184+
writeChar(buf, c);
185+
}
163186
};
164187

165188
struct JsonStringBuffer

test/codectestjson.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,50 @@ GIVEN( "non-finite real numbers" )
250250
}
251251
}
252252
}
253+
GIVEN( "a string Variant with control characters" )
254+
{
255+
std::string s;
256+
for (char c = 0; c<=0x1f; ++c)
257+
s += c;
258+
s += '\"';
259+
s += '\\';
260+
Variant v = s;
261+
262+
WHEN( "encoding to JSON and decoding back" )
263+
{
264+
std::string encoded;
265+
Json::encode(v, encoded);
266+
Variant decoded;
267+
Json::decode(encoded, decoded);
268+
269+
THEN( "the decoded Variant matches the original" )
270+
{
271+
CHECK( decoded == v );
272+
}
273+
}
274+
}
275+
GIVEN( "an object Variant with control characters in a key" )
276+
{
277+
std::string key;
278+
for (char c = 0; c<=0x1f; ++c)
279+
key += c;
280+
key += '\"';
281+
key += '\\';
282+
Variant v = Object{{{key, 123}}};
283+
284+
WHEN( "encoding to JSON and decoding back" )
285+
{
286+
std::string encoded;
287+
Json::encode(v, encoded);
288+
Variant decoded;
289+
Json::decode(encoded, decoded);
290+
291+
THEN( "the decoded Variant matches the original" )
292+
{
293+
CHECK( decoded == v );
294+
}
295+
}
296+
}
253297
}
254298

255299
#endif // #if CPPWAMP_TESTING_CODEC

test/varianttestconverter.cpp

Lines changed: 77 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
using namespace wamp;
1515

16-
namespace
16+
namespace user
1717
{
1818

1919
//------------------------------------------------------------------------------
@@ -199,8 +199,33 @@ class CustomContainer
199199

200200
CPPWAMP_CONVERSION_SPLIT_MEMBER(CustomContainer)
201201

202-
} // anonymous namespace
202+
//------------------------------------------------------------------------------
203+
class DerivedDto : public SimpleDto
204+
{
205+
public:
206+
std::string extra;
203207

208+
bool operator==(const DerivedDto& other) const
209+
{
210+
return (static_cast<const Base&>(*this) ==
211+
static_cast<const Base&>(other)) &&
212+
(extra == other.extra);
213+
}
214+
215+
private:
216+
using Base = SimpleDto;
217+
218+
template <typename TConverter>
219+
void convert(TConverter& conv)
220+
{
221+
conv(static_cast<Base&>(*this))
222+
("extra", extra);
223+
}
224+
225+
friend class wamp::ConversionAccess;
226+
};
227+
228+
} // namespace user
204229

205230

206231
//------------------------------------------------------------------------------
@@ -213,7 +238,7 @@ SCENARIO( "Using converters directly", "[Variant]" )
213238

214239
GIVEN( "a simple DTO" )
215240
{
216-
SimpleDto dto{true, 2, 3.0f, "4"};
241+
user::SimpleDto dto{true, 2, 3.0f, "4"};
217242

218243
WHEN( "Saving the DTO" )
219244
{
@@ -224,15 +249,15 @@ SCENARIO( "Using converters directly", "[Variant]" )
224249
WHEN( "Loading the DTO" )
225250
{
226251
v = object;
227-
SimpleDto loaded;
252+
user::SimpleDto loaded;
228253
fromConverter(loaded);
229254
CHECK( loaded == dto );
230255
}
231256
}
232257

233258
GIVEN( "a simple DTO with intrusive converter" )
234259
{
235-
IntrusiveSimpleDto dto{true, 2, 3.0f, "4"};
260+
user::IntrusiveSimpleDto dto{true, 2, 3.0f, "4"};
236261

237262
WHEN( "Saving the DTO" )
238263
{
@@ -243,7 +268,7 @@ SCENARIO( "Using converters directly", "[Variant]" )
243268
WHEN( "Loading the DTO" )
244269
{
245270
v = object;
246-
IntrusiveSimpleDto loaded;
271+
user::IntrusiveSimpleDto loaded;
247272
convert(fromConverter, loaded);
248273
CHECK( loaded == dto );
249274
}
@@ -257,7 +282,7 @@ SCENARIO( "Converting to/from variants", "[Variant]" )
257282

258283
GIVEN( "a simple DTO" )
259284
{
260-
SimpleDto dto{true, 2, 3.0f, "4"};
285+
user::SimpleDto dto{true, 2, 3.0f, "4"};
261286

262287
WHEN( "Saving the DTO" )
263288
{
@@ -268,14 +293,14 @@ SCENARIO( "Converting to/from variants", "[Variant]" )
268293
WHEN( "Loading the DTO" )
269294
{
270295
Variant v = object;
271-
auto loaded = v.to<SimpleDto>();
296+
auto loaded = v.to<user::SimpleDto>();
272297
CHECK( loaded == dto );
273298
}
274299
}
275300

276301
GIVEN( "a simple DTO with intrusive converter" )
277302
{
278-
IntrusiveSimpleDto dto{true, 2, 3.0f, "4"};
303+
user::IntrusiveSimpleDto dto{true, 2, 3.0f, "4"};
279304

280305
WHEN( "Saving the DTO" )
281306
{
@@ -286,7 +311,7 @@ SCENARIO( "Converting to/from variants", "[Variant]" )
286311
WHEN( "Loading the DTO" )
287312
{
288313
Variant v = object;
289-
auto loaded = v.to<IntrusiveSimpleDto>();
314+
auto loaded = v.to<user::IntrusiveSimpleDto>();
290315
CHECK( loaded == dto );
291316
}
292317
}
@@ -301,7 +326,8 @@ SCENARIO( "Composite DTOs", "[Variant]" )
301326

302327
GIVEN( "a composite DTO" )
303328
{
304-
CompositeDto dto{ {true, 2, 3.0f, "4"}, {false, -2, -3.0f, "-4"} };
329+
user::CompositeDto dto{ {true, 2, 3.0f, "4"},
330+
{false, -2, -3.0f, "-4"} };
305331

306332
WHEN( "Saving the DTO" )
307333
{
@@ -312,15 +338,15 @@ SCENARIO( "Composite DTOs", "[Variant]" )
312338
WHEN( "Loading the DTO" )
313339
{
314340
Variant v = compositeObject;
315-
auto loaded = v.to<CompositeDto>();
341+
auto loaded = v.to<user::CompositeDto>();
316342
CHECK( loaded == dto );
317343
}
318344
}
319345

320346
GIVEN( "a simple DTO with intrusive converter" )
321347
{
322-
IntrusiveCompositeDto dto{ {true, 2, 3.0f, "4"},
323-
{false, -2, -3.0f, "-4"} };
348+
user::IntrusiveCompositeDto dto{ {true, 2, 3.0f, "4"},
349+
{false, -2, -3.0f, "-4"} };
324350

325351
WHEN( "Saving the DTO" )
326352
{
@@ -331,7 +357,7 @@ SCENARIO( "Composite DTOs", "[Variant]" )
331357
WHEN( "Loading the DTO" )
332358
{
333359
Variant v = compositeObject;
334-
auto loaded = v.to<IntrusiveCompositeDto>();
360+
auto loaded = v.to<user::IntrusiveCompositeDto>();
335361
CHECK( loaded == dto );
336362
}
337363
}
@@ -345,7 +371,7 @@ SCENARIO( "Using split conversions", "[Variant]" )
345371

346372
GIVEN( "a simple DTO" )
347373
{
348-
SplitDto dto{true, 2, 3.0f, "4"};
374+
user::SplitDto dto{true, 2, 3.0f, "4"};
349375

350376
WHEN( "Saving the DTO" )
351377
{
@@ -356,14 +382,14 @@ SCENARIO( "Using split conversions", "[Variant]" )
356382
WHEN( "Loading the DTO" )
357383
{
358384
Variant v = object1;
359-
auto loaded = v.to<SplitDto>();
385+
auto loaded = v.to<user::SplitDto>();
360386
CHECK( loaded == dto );
361387
}
362388
}
363389

364390
GIVEN( "a simple DTO with intrusive converter" )
365391
{
366-
IntrusiveSplitDto dto{true, 2, 3.0f, "4"};
392+
user::IntrusiveSplitDto dto{true, 2, 3.0f, "4"};
367393

368394
WHEN( "Saving the DTO" )
369395
{
@@ -374,7 +400,7 @@ SCENARIO( "Using split conversions", "[Variant]" )
374400
WHEN( "Loading the DTO" )
375401
{
376402
Variant v = object1;
377-
auto loaded = v.to<IntrusiveSplitDto>();
403+
auto loaded = v.to<user::IntrusiveSplitDto>();
378404
CHECK( loaded == dto );
379405
}
380406
}
@@ -385,7 +411,7 @@ SCENARIO( "Converting custom sequence collections", "[Variant]" )
385411
{
386412
GIVEN( "a custom sequence collection" )
387413
{
388-
CustomContainer seq;
414+
user::CustomContainer seq;
389415
seq.data = {1, 2, 3};
390416

391417
WHEN( "converted to a variant" )
@@ -405,7 +431,7 @@ SCENARIO( "Converting custom sequence collections", "[Variant]" )
405431

406432
WHEN( "converted to a custom sequence collection" )
407433
{
408-
auto seq = v.to<CustomContainer>();
434+
auto seq = v.to<user::CustomContainer>();
409435

410436
THEN( "the collection is as expected" )
411437
{
@@ -415,4 +441,34 @@ SCENARIO( "Converting custom sequence collections", "[Variant]" )
415441
}
416442
}
417443

444+
//------------------------------------------------------------------------------
445+
SCENARIO( "Derived DTOs", "[Variant]" )
446+
{
447+
Variant derivedObject = Object{{"b",true}, {"n",2}, {"x",3.0f}, {"s","4"},
448+
{"extra", "5"}};
449+
450+
GIVEN( "a derived DTO" )
451+
{
452+
user::DerivedDto dto;
453+
dto.b = true;
454+
dto.n = 2;
455+
dto.x = 3.0f;
456+
dto.s = "4";
457+
dto.extra = "5";
458+
459+
WHEN( "Saving the DTO" )
460+
{
461+
auto v = Variant::from(dto);
462+
CHECK( v == derivedObject );
463+
}
464+
465+
WHEN( "Loading the DTO" )
466+
{
467+
Variant v = derivedObject;
468+
auto loaded = v.to<user::DerivedDto>();
469+
CHECK( loaded == dto );
470+
}
471+
}
472+
}
473+
418474
#endif // #if CPPWAMP_TESTING_VARIANT

0 commit comments

Comments
 (0)