Unverified Commit 3f928587 authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #628 from capnproto/json-omit-defaults

Add option to omit default-valued primitives from JSON output.
parents 328e6f5b 71721e5c
......@@ -49,6 +49,7 @@ struct FieldHash {
struct JsonCodec::Impl {
bool prettyPrint = false;
HasMode hasMode = HasMode::NON_NULL;
size_t maxNestingDepth = 64;
std::unordered_map<Type, HandlerBase*, TypeHash> typeHandlers;
......@@ -195,6 +196,8 @@ void JsonCodec::setMaxNestingDepth(size_t maxNestingDepth) {
impl->maxNestingDepth = maxNestingDepth;
}
void JsonCodec::setHasMode(HasMode mode) { impl->hasMode = mode; }
kj::String JsonCodec::encode(DynamicValue::Reader value, Type type) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
......@@ -308,7 +311,7 @@ void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder
uint fieldCount = 0;
for (auto i: kj::indices(nonUnionFields)) {
fieldCount += (hasField[i] = structValue.has(nonUnionFields[i]));
fieldCount += (hasField[i] = structValue.has(nonUnionFields[i], impl->hasMode));
}
// We try to write the union field, if any, in proper order with the rest.
......@@ -318,7 +321,7 @@ void JsonCodec::encode(DynamicValue::Reader input, Type type, JsonValue::Builder
KJ_IF_MAYBE(field, which) {
// Even if the union field is null, if it is not the default field of the union then we
// have to print it anyway.
unionFieldIsNull = !structValue.has(*field);
unionFieldIsNull = !structValue.has(*field, impl->hasMode);
if (field->getProto().getDiscriminantValue() != 0 || !unionFieldIsNull) {
++fieldCount;
} else {
......
......@@ -74,6 +74,12 @@ public:
// Set maximum nesting depth when decoding JSON to prevent highly nested input from overflowing
// the call stack. The default is 64.
void setHasMode(HasMode mode);
// Normally, primitive field values are always included even if they are equal to the default
// value (HasMode::NON_NULL -- only null pointers are omitted). You can use
// setHasMode(HasMode::NON_DEFAULT) to specify that default-valued primitive fields should be
// omitted as well.
template <typename T>
kj::String encode(T&& value) const;
// Encode any Cap'n Proto value to JSON, including primitives and
......
......@@ -422,15 +422,20 @@ TEST(DynamicApi, Has) {
// Primitive fields are always present even if set to default.
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
root.set("int32Field", 123);
EXPECT_TRUE(root.has("int32Field"));
EXPECT_TRUE(root.has("int32Field", HasMode::NON_DEFAULT));
root.set("int32Field", -12345678);
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
// Pointers are absent until initialized.
EXPECT_FALSE(root.has("structField"));
EXPECT_FALSE(root.has("structField", HasMode::NON_DEFAULT));
root.init("structField");
EXPECT_TRUE(root.has("structField"));
EXPECT_TRUE(root.has("structField", HasMode::NON_DEFAULT));
}
TEST(DynamicApi, HasWhenEmpty) {
......@@ -443,6 +448,11 @@ TEST(DynamicApi, HasWhenEmpty) {
EXPECT_TRUE(root.has("int32Field"));
EXPECT_FALSE(root.has("structField"));
EXPECT_FALSE(root.has("int32List"));
EXPECT_FALSE(root.has("voidField", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("int32Field", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("structField", HasMode::NON_DEFAULT));
EXPECT_FALSE(root.has("int32List", HasMode::NON_DEFAULT));
}
TEST(DynamicApi, SetEnumFromNative) {
......
......@@ -414,7 +414,7 @@ DynamicValue::Pipeline DynamicStruct::Pipeline::get(StructSchema::Field field) {
KJ_UNREACHABLE;
}
bool DynamicStruct::Reader::has(StructSchema::Field field) const {
bool DynamicStruct::Reader::has(StructSchema::Field field, HasMode mode) const {
KJ_REQUIRE(field.getContainingStruct() == schema, "`field` is not a field of this struct.");
auto proto = field.getProto();
......@@ -441,20 +441,35 @@ bool DynamicStruct::Reader::has(StructSchema::Field field) const {
switch (type.which()) {
case schema::Type::VOID:
// Void is always equal to the default.
return mode == HasMode::NON_NULL;
case schema::Type::BOOL:
return mode == HasMode::NON_NULL ||
reader.getDataField<bool>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT8:
case schema::Type::INT16:
case schema::Type::INT32:
case schema::Type::INT64:
case schema::Type::UINT8:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint8_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT16:
case schema::Type::UINT16:
case schema::Type::ENUM:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint16_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT32:
case schema::Type::UINT32:
case schema::Type::UINT64:
case schema::Type::FLOAT32:
return mode == HasMode::NON_NULL ||
reader.getDataField<uint32_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::INT64:
case schema::Type::UINT64:
case schema::Type::FLOAT64:
case schema::Type::ENUM:
// Primitive types are always present.
return true;
return mode == HasMode::NON_NULL ||
reader.getDataField<uint64_t>(assumeDataOffset(slot.getOffset()), 0) != 0;
case schema::Type::TEXT:
case schema::Type::DATA:
......@@ -985,11 +1000,11 @@ DynamicValue::Builder DynamicStruct::Builder::get(kj::StringPtr name) {
DynamicValue::Pipeline DynamicStruct::Pipeline::get(kj::StringPtr name) {
return get(schema.getFieldByName(name));
}
bool DynamicStruct::Reader::has(kj::StringPtr name) const {
return has(schema.getFieldByName(name));
bool DynamicStruct::Reader::has(kj::StringPtr name, HasMode mode) const {
return has(schema.getFieldByName(name), mode);
}
bool DynamicStruct::Builder::has(kj::StringPtr name) {
return has(schema.getFieldByName(name));
bool DynamicStruct::Builder::has(kj::StringPtr name, HasMode mode) {
return has(schema.getFieldByName(name), mode);
}
void DynamicStruct::Builder::set(kj::StringPtr name, const DynamicValue::Reader& value) {
set(schema.getFieldByName(name), value);
......
......@@ -169,6 +169,21 @@ private:
// -------------------------------------------------------------------
enum class HasMode: uint8_t {
// Specifies the meaning of "has(field)".
NON_NULL,
// "has(field)" only returns false if the field is a pointer and the pointer is null. This is the
// default behavior.
NON_DEFAULT
// "has(field)" returns false if the field is set to its default value. This differs from
// NON_NULL only in the handling of primitive values.
//
// "Equal to default value" is technically defined as the field value being encoded as all-zero
// on the wire (since primitive values are XORed by their defined default value when encoded).
};
class DynamicStruct::Reader {
public:
typedef DynamicStruct Reads;
......@@ -191,12 +206,10 @@ public:
DynamicValue::Reader get(StructSchema::Field field) const;
// Read the given field value.
bool has(StructSchema::Field field) const;
// Tests whether the given field is set to its default value. For pointer values, this does
// not actually traverse the value comparing it with the default, but simply returns true if the
// pointer is non-null. For members of unions, has() returns false if the union member is not
// active, but does not necessarily return true if the member is active (depends on the field's
// value).
bool has(StructSchema::Field field, HasMode mode = HasMode::NON_NULL) const;
// Tests whether the given field is "present". If the field is a union member and is not the
// active member, this always returns false. Otherwise, the field's value is interpreted
// according to `mode`.
kj::Maybe<StructSchema::Field> which() const;
// If the struct contains an (unnamed) union, and the currently-active field within that union
......@@ -206,7 +219,7 @@ public:
// newer version of the protocol and is using a field of the union that you don't know about yet.
DynamicValue::Reader get(kj::StringPtr name) const;
bool has(kj::StringPtr name) const;
bool has(kj::StringPtr name, HasMode mode = HasMode::NON_NULL) const;
// Shortcuts to access fields by name. These throw exceptions if no such field exists.
private:
......@@ -261,12 +274,11 @@ public:
DynamicValue::Builder get(StructSchema::Field field);
// Read the given field value.
inline bool has(StructSchema::Field field) { return asReader().has(field); }
// Tests whether the given field is set to its default value. For pointer values, this does
// not actually traverse the value comparing it with the default, but simply returns true if the
// pointer is non-null. For members of unions, has() returns whether the field is currently
// active and the union as a whole is non-default -- so, the only time has() will return false
// for an active union field is if it is the default active field and it has its default value.
inline bool has(StructSchema::Field field, HasMode mode = HasMode::NON_NULL)
{ return asReader().has(field, mode); }
// Tests whether the given field is "present". If the field is a union member and is not the
// active member, this always returns false. Otherwise, the field's value is interpreted
// according to `mode`.
kj::Maybe<StructSchema::Field> which();
// If the struct contains an (unnamed) union, and the currently-active field within that union
......@@ -292,7 +304,7 @@ public:
// field null.
DynamicValue::Builder get(kj::StringPtr name);
bool has(kj::StringPtr name);
bool has(kj::StringPtr name, HasMode mode = HasMode::NON_NULL);
void set(kj::StringPtr name, const DynamicValue::Reader& value);
void set(kj::StringPtr name, std::initializer_list<DynamicValue::Reader> value);
DynamicValue::Builder init(kj::StringPtr name);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment