Skip to content

Commit 9141732

Browse files
authored
feat: omit empty items from data model JSON serialization (#309)
The server-side SDK is able to treat missing JSON fields as empty ones in-memory. For example, in a segment if `included` was missing then the SDK infers it's an empty array. This change makes the SDK write out data using that same logic. If an in-memory field is conceptually empty, then omit it from the written data. This is an optimization, so the only places updated in this commit are top-level flag segment models which are frequently used in unit tests where we'd like to omit missing fields.
1 parent 08d8872 commit 9141732

File tree

4 files changed

+56
-24
lines changed

4 files changed

+56
-24
lines changed

libs/internal/include/launchdarkly/serialization/value_mapping.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,25 @@ void WriteMinimal(boost::json::object& obj,
166166
}
167167
}
168168

169+
template <typename T>
170+
void WriteMinimal(boost::json::object& obj,
171+
std::string const& key,
172+
std::vector<T> const& val) {
173+
if (!val.empty()) {
174+
obj.emplace(key, boost::json::value_from(val));
175+
}
176+
}
177+
178+
template <typename T>
179+
void WriteMinimal(boost::json::object& obj,
180+
std::string const& key,
181+
T const& val,
182+
std::function<bool()> const& predicate) {
183+
if (predicate()) {
184+
obj.emplace(key, boost::json::value_from(val));
185+
}
186+
}
187+
169188
void WriteMinimal(boost::json::object& obj,
170189
std::string const& key, // No copy when not used.
171190
bool val);

libs/internal/src/serialization/json_flag.cpp

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -349,19 +349,35 @@ void tag_invoke(boost::json::value_from_tag const& unused,
349349
WriteMinimal(obj, "offVariation", flag.offVariation);
350350
obj.emplace("key", flag.key);
351351
obj.emplace("version", flag.version);
352-
obj.emplace("variations", boost::json::value_from(flag.variations));
353-
obj.emplace("rules", boost::json::value_from(flag.rules));
354-
obj.emplace("prerequisites", boost::json::value_from(flag.prerequisites));
355-
obj.emplace("fallthrough", boost::json::value_from(flag.fallthrough));
356-
obj.emplace("clientSideAvailability",
357-
boost::json::value_from(flag.clientSideAvailability));
358-
obj.emplace("contextTargets", boost::json::value_from(flag.contextTargets));
352+
WriteMinimal(obj, "variations", flag.variations);
353+
WriteMinimal(obj, "rules", flag.rules);
354+
WriteMinimal(obj, "prerequisites", flag.prerequisites);
355+
WriteMinimal(obj, "fallthrough", flag.fallthrough, [&]() {
356+
return std::visit(
357+
[&](auto&& arg) {
358+
using T = std::decay_t<decltype(arg)>;
359+
if constexpr (std::is_same_v<T,
360+
std::optional<Flag::Variation>>) {
361+
return arg.has_value();
362+
} else if constexpr (std::is_same_v<T, Flag::Rollout>) {
363+
return true;
364+
}
365+
},
366+
flag.fallthrough);
367+
});
368+
WriteMinimal(obj, "clientSideAvailability", flag.clientSideAvailability,
369+
[&]() {
370+
return flag.clientSideAvailability.usingEnvironmentId ||
371+
flag.clientSideAvailability.usingMobileKey;
372+
});
373+
WriteMinimal(obj, "contextTargets", flag.contextTargets);
359374

360375
std::vector<UserTarget> user_targets;
361376
for (auto const& target : flag.targets) {
362377
user_targets.emplace_back(target);
363378
}
364-
obj.emplace("targets", boost::json::value_from(user_targets));
379+
380+
WriteMinimal(obj, "targets", user_targets);
365381
}
366382

367383
} // namespace data_model

libs/internal/src/serialization/json_segment.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,11 @@ void tag_invoke(boost::json::value_from_tag const& unused,
114114
WriteMinimal(obj, "unboundedContextKind", segment.unboundedContextKind);
115115
WriteMinimal(obj, "unbounded", segment.unbounded);
116116

117-
obj.emplace("rules", boost::json::value_from(segment.rules));
118-
obj.emplace("excluded", boost::json::value_from(segment.excluded));
119-
obj.emplace("excludedContexts",
120-
boost::json::value_from(segment.excludedContexts));
121-
obj.emplace("included", boost::json::value_from(segment.included));
122-
obj.emplace("includedContexts",
123-
boost::json::value_from(segment.includedContexts));
117+
WriteMinimal(obj, "rules", segment.rules);
118+
WriteMinimal(obj, "excluded", segment.excluded);
119+
WriteMinimal(obj, "excludedContexts", segment.excludedContexts);
120+
WriteMinimal(obj, "included", segment.included);
121+
WriteMinimal(obj, "includedContexts", segment.includedContexts);
124122
}
125123

126124
void tag_invoke(boost::json::value_from_tag const& unused,

libs/internal/tests/data_model_serialization_test.cpp

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -600,9 +600,13 @@ TEST(FlagTests, SerializeAll) {
600600
},
601601
24,
602602
ContextKind("bob")}}, // contextTargets
603-
{}, // rules
604-
84, // offVariation
605-
true, // clientSide
603+
{data_model::Flag::Rule{{data_model::Clause{data_model::Clause::Op::kIn,
604+
{"bob"},
605+
false,
606+
ContextKind{"user"},
607+
"name"}}}}, // rules
608+
84, // offVariation
609+
true, // clientSide
606610
data_model::Flag::ClientSideAvailability{true, true},
607611
"4242", // salt
608612
true, // trackEvents
@@ -623,7 +627,7 @@ TEST(FlagTests, SerializeAll) {
623627
"key":"the-key",
624628
"version":21,
625629
"variations":["a","b"],
626-
"rules":[],
630+
"rules":[{"clauses":[{"op":"in","values":["bob"],"contextKind":"user","attribute":"name"}]}],
627631
"prerequisites":[{"key":"prereqA","variation":2},
628632
{"key":"prereqB","variation":3}],
629633
"fallthrough":{"variation":42},
@@ -739,12 +743,7 @@ TEST(SegmentTests, SerializeUnbounded) {
739743
R"({
740744
"key": "my-segment",
741745
"version": 87,
742-
"included": [],
743-
"excluded": [],
744-
"includedContexts": [],
745-
"excludedContexts": [],
746746
"salt": "salty",
747-
"rules":[],
748747
"unbounded": true,
749748
"unboundedContextKind": "company",
750749
"generation": 12

0 commit comments

Comments
 (0)