cannam@62: // Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors cannam@62: // Licensed under the MIT License: cannam@62: // cannam@62: // Permission is hereby granted, free of charge, to any person obtaining a copy cannam@62: // of this software and associated documentation files (the "Software"), to deal cannam@62: // in the Software without restriction, including without limitation the rights cannam@62: // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell cannam@62: // copies of the Software, and to permit persons to whom the Software is cannam@62: // furnished to do so, subject to the following conditions: cannam@62: // cannam@62: // The above copyright notice and this permission notice shall be included in cannam@62: // all copies or substantial portions of the Software. cannam@62: // cannam@62: // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR cannam@62: // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, cannam@62: // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE cannam@62: // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER cannam@62: // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, cannam@62: // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN cannam@62: // THE SOFTWARE. cannam@62: cannam@62: #ifndef CAPNP_COMPAT_JSON_H_ cannam@62: #define CAPNP_COMPAT_JSON_H_ cannam@62: cannam@62: #include cannam@62: #include cannam@62: #include cannam@62: cannam@62: namespace capnp { cannam@62: cannam@62: class JsonCodec { cannam@62: // Flexible class for encoding Cap'n Proto types as JSON, and decoding JSON back to Cap'n Proto. cannam@62: // cannam@62: // Typical usage: cannam@62: // cannam@62: // JsonCodec json; cannam@62: // cannam@62: // // encode cannam@62: // kj::String encoded = json.encode(someStructReader); cannam@62: // cannam@62: // // decode cannam@62: // json.decode(encoded, someStructBuilder); cannam@62: // cannam@62: // Advanced users can do fancy things like override the way certain types or fields are cannam@62: // represented in JSON by registering handlers. See the unit test for an example. cannam@62: // cannam@62: // Notes: cannam@62: // - When encoding, all primitive fields are always encoded, even if default-valued. Pointer cannam@62: // fields are only encoded if they are non-null. cannam@62: // - 64-bit integers are encoded as strings, since JSON "numbers" are double-precision floating cannam@62: // points which cannot store a 64-bit integer without losing data. cannam@62: // - NaNs and infinite floating point numbers are not allowed by the JSON spec, and so are encoded cannam@62: // as null. This matches the behavior of `JSON.stringify` in at least Firefox and Chrome. cannam@62: // - Data is encoded as an array of numbers in the range [0,255]. You probably want to register cannam@62: // a handler that does something better, like maybe base64 encoding, but there are a zillion cannam@62: // different ways people do this. cannam@62: // - Encoding/decoding capabilities and AnyPointers requires registering a Handler, since there's cannam@62: // no obvious default behavior. cannam@62: // - When decoding, unrecognized field names are ignored. Note: This means that JSON is NOT a cannam@62: // good format for receiving input from a human. Consider `capnp eval` or the SchemaParser cannam@62: // library for human input. cannam@62: cannam@62: public: cannam@62: JsonCodec(); cannam@62: ~JsonCodec() noexcept(false); cannam@62: cannam@62: // --------------------------------------------------------------------------- cannam@62: // standard API cannam@62: cannam@62: void setPrettyPrint(bool enabled); cannam@62: // Enable to insert newlines, indentation, and other extra spacing into the output. The default cannam@62: // is to use minimal whitespace. cannam@62: cannam@62: void setMaxNestingDepth(size_t maxNestingDepth); cannam@62: // Set maximum nesting depth when decoding JSON to prevent highly nested input from overflowing cannam@62: // the call stack. The default is 64. cannam@62: cannam@62: template cannam@62: kj::String encode(T&& value); cannam@62: // Encode any Cap'n Proto value to JSON, including primitives and cannam@62: // Dynamic{Enum,Struct,List,Capability}, but not DynamicValue (see below). cannam@62: cannam@62: kj::String encode(DynamicValue::Reader value, Type type) const; cannam@62: // Encode a DynamicValue to JSON. `type` is needed because `DynamicValue` itself does cannam@62: // not distinguish between e.g. int32 and int64, which in JSON are handled differently. Most cannam@62: // of the time, though, you can use the single-argument templated version of `encode()` instead. cannam@62: cannam@62: void decode(kj::ArrayPtr input, DynamicStruct::Builder output) const; cannam@62: // Decode JSON text directly into a struct builder. This only works for structs since lists cannam@62: // need to be allocated with the correct size in advance. cannam@62: // cannam@62: // (Remember that any Cap'n Proto struct reader type can be implicitly cast to cannam@62: // DynamicStruct::Reader.) cannam@62: cannam@62: template cannam@62: Orphan decode(kj::ArrayPtr input, Orphanage orphanage) const; cannam@62: // Decode JSON text to any Cap'n Proto object (pointer value), allocated using the given cannam@62: // orphanage. T must be specified explicitly and cannot be dynamic, e.g.: cannam@62: // cannam@62: // Orphan orphan = json.decode(text, orphanage); cannam@62: cannam@62: template cannam@62: ReaderFor decode(kj::ArrayPtr input) const; cannam@62: // Decode JSON text into a primitive or capability value. T must be specified explicitly and cannam@62: // cannot be dynamic, e.g.: cannam@62: // cannam@62: // uint32_t n = json.decode(text); cannam@62: cannam@62: Orphan decode(kj::ArrayPtr input, Type type, Orphanage orphanage) const; cannam@62: Orphan decode( cannam@62: kj::ArrayPtr input, ListSchema type, Orphanage orphanage) const; cannam@62: Orphan decode( cannam@62: kj::ArrayPtr input, StructSchema type, Orphanage orphanage) const; cannam@62: DynamicCapability::Client decode(kj::ArrayPtr input, InterfaceSchema type) const; cannam@62: DynamicEnum decode(kj::ArrayPtr input, EnumSchema type) const; cannam@62: // Decode to a dynamic value, specifying the type schema. cannam@62: cannam@62: // --------------------------------------------------------------------------- cannam@62: // layered API cannam@62: // cannam@62: // You can separate text <-> JsonValue from JsonValue <-> T. These are particularly useful cannam@62: // for calling from Handler implementations. cannam@62: cannam@62: kj::String encodeRaw(JsonValue::Reader value) const; cannam@62: void decodeRaw(kj::ArrayPtr input, JsonValue::Builder output) const; cannam@62: // Translate JsonValue <-> text. cannam@62: cannam@62: template cannam@62: void encode(T&& value, JsonValue::Builder output); cannam@62: void encode(DynamicValue::Reader input, Type type, JsonValue::Builder output) const; cannam@62: void decode(JsonValue::Reader input, DynamicStruct::Builder output) const; cannam@62: template cannam@62: Orphan decode(JsonValue::Reader input, Orphanage orphanage) const; cannam@62: template cannam@62: ReaderFor decode(JsonValue::Reader input) const; cannam@62: cannam@62: Orphan decode(JsonValue::Reader input, Type type, Orphanage orphanage) const; cannam@62: Orphan decode(JsonValue::Reader input, ListSchema type, Orphanage orphanage) const; cannam@62: Orphan decode( cannam@62: JsonValue::Reader input, StructSchema type, Orphanage orphanage) const; cannam@62: DynamicCapability::Client decode(JsonValue::Reader input, InterfaceSchema type) const; cannam@62: DynamicEnum decode(JsonValue::Reader input, EnumSchema type) const; cannam@62: cannam@62: // --------------------------------------------------------------------------- cannam@62: // specializing particular types cannam@62: cannam@62: template ()> cannam@62: class Handler; cannam@62: // Implement this interface to specify a special encoding for a particular type or field. cannam@62: // cannam@62: // The templates are a bit ugly, but subclasses of this type essentially implement two methods, cannam@62: // one to encode values of this type and one to decode values of this type. `encode()` is simple: cannam@62: // cannam@62: // void encode(const JsonCodec& codec, ReaderFor input, JsonValue::Builder output) const; cannam@62: // cannam@62: // `decode()` is a bit trickier. When T is a struct (including DynamicStruct), it is: cannam@62: // cannam@62: // void decode(const JsonCodec& codec, JsonValue::Reader input, BuilderFor output) const; cannam@62: // cannam@62: // However, when T is a primitive, decode() is: cannam@62: // cannam@62: // T decode(const JsonCodec& codec, JsonValue::Reader input) const; cannam@62: // cannam@62: // Or when T is any non-struct object (list, blob), decode() is: cannam@62: // cannam@62: // Orphan decode(const JsonCodec& codec, JsonValue::Reader input, Orphanage orphanage) const; cannam@62: // cannam@62: // Or when T is an interface: cannam@62: // cannam@62: // T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const; cannam@62: // cannam@62: // Additionally, when T is a struct you can *optionally* also implement the orphan-returning form cannam@62: // of decode(), but it will only be called when the struct would be allocated as an individual cannam@62: // object, not as part of a list. This allows you to return "nullptr" in these cases to say that cannam@62: // the pointer value should be null. This does not apply to list elements because struct list cannam@62: // elements cannot ever be null (since Cap'n Proto encodes struct lists as a flat list rather cannam@62: // than list-of-pointers). cannam@62: cannam@62: template cannam@62: void addTypeHandler(Handler& handler); cannam@62: void addTypeHandler(Type type, Handler& handler); cannam@62: void addTypeHandler(EnumSchema type, Handler& handler); cannam@62: void addTypeHandler(StructSchema type, Handler& handler); cannam@62: void addTypeHandler(ListSchema type, Handler& handler); cannam@62: void addTypeHandler(InterfaceSchema type, Handler& handler); cannam@62: // Arrange that whenever the type T appears in the message, your handler will be used to cannam@62: // encode/decode it. cannam@62: // cannam@62: // Note that if you register a handler for a capability type, it will also apply to subtypes. cannam@62: // Thus Handler handles all capabilities. cannam@62: cannam@62: template cannam@62: void addFieldHandler(StructSchema::Field field, Handler& handler); cannam@62: // Matches only the specific field. T can be a dynamic type. T must match the field's type. cannam@62: cannam@62: private: cannam@62: class HandlerBase; cannam@62: struct Impl; cannam@62: cannam@62: kj::Own impl; cannam@62: cannam@62: void encodeField(StructSchema::Field field, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const; cannam@62: void decodeArray(List::Reader input, DynamicList::Builder output) const; cannam@62: void decodeObject(List::Reader input, DynamicStruct::Builder output) const; cannam@62: void addTypeHandlerImpl(Type type, HandlerBase& handler); cannam@62: void addFieldHandlerImpl(StructSchema::Field field, Type type, HandlerBase& handler); cannam@62: }; cannam@62: cannam@62: // ======================================================================================= cannam@62: // inline implementation details cannam@62: cannam@62: template cannam@62: kj::String JsonCodec::encode(T&& value) { cannam@62: typedef FromAny> Base; cannam@62: return encode(DynamicValue::Reader(ReaderFor(kj::fwd(value))), Type::from()); cannam@62: } cannam@62: cannam@62: template cannam@62: inline Orphan JsonCodec::decode(kj::ArrayPtr input, Orphanage orphanage) const { cannam@62: return decode(input, Type::from(), orphanage).template releaseAs(); cannam@62: } cannam@62: cannam@62: template cannam@62: inline ReaderFor JsonCodec::decode(kj::ArrayPtr input) const { cannam@62: static_assert(style() == Style::PRIMITIVE || style() == Style::CAPABILITY, cannam@62: "must specify an orphanage to decode an object type"); cannam@62: return decode(input, Type::from(), Orphanage()).getReader().template as(); cannam@62: } cannam@62: cannam@62: inline Orphan JsonCodec::decode( cannam@62: kj::ArrayPtr input, ListSchema type, Orphanage orphanage) const { cannam@62: return decode(input, Type(type), orphanage).releaseAs(); cannam@62: } cannam@62: inline Orphan JsonCodec::decode( cannam@62: kj::ArrayPtr input, StructSchema type, Orphanage orphanage) const { cannam@62: return decode(input, Type(type), orphanage).releaseAs(); cannam@62: } cannam@62: inline DynamicCapability::Client JsonCodec::decode( cannam@62: kj::ArrayPtr input, InterfaceSchema type) const { cannam@62: return decode(input, Type(type), Orphanage()).getReader().as(); cannam@62: } cannam@62: inline DynamicEnum JsonCodec::decode(kj::ArrayPtr input, EnumSchema type) const { cannam@62: return decode(input, Type(type), Orphanage()).getReader().as(); cannam@62: } cannam@62: cannam@62: // ----------------------------------------------------------------------------- cannam@62: cannam@62: template cannam@62: void JsonCodec::encode(T&& value, JsonValue::Builder output) { cannam@62: typedef FromAny> Base; cannam@62: encode(DynamicValue::Reader(ReaderFor(kj::fwd(value))), Type::from(), output); cannam@62: } cannam@62: cannam@62: template cannam@62: inline Orphan JsonCodec::decode(JsonValue::Reader input, Orphanage orphanage) const { cannam@62: return decode(input, Type::from(), orphanage).template releaseAs(); cannam@62: } cannam@62: cannam@62: template cannam@62: inline ReaderFor JsonCodec::decode(JsonValue::Reader input) const { cannam@62: static_assert(style() == Style::PRIMITIVE || style() == Style::CAPABILITY, cannam@62: "must specify an orphanage to decode an object type"); cannam@62: return decode(input, Type::from(), Orphanage()).getReader().template as(); cannam@62: } cannam@62: cannam@62: inline Orphan JsonCodec::decode( cannam@62: JsonValue::Reader input, ListSchema type, Orphanage orphanage) const { cannam@62: return decode(input, Type(type), orphanage).releaseAs(); cannam@62: } cannam@62: inline Orphan JsonCodec::decode( cannam@62: JsonValue::Reader input, StructSchema type, Orphanage orphanage) const { cannam@62: return decode(input, Type(type), orphanage).releaseAs(); cannam@62: } cannam@62: inline DynamicCapability::Client JsonCodec::decode( cannam@62: JsonValue::Reader input, InterfaceSchema type) const { cannam@62: return decode(input, Type(type), Orphanage()).getReader().as(); cannam@62: } cannam@62: inline DynamicEnum JsonCodec::decode(JsonValue::Reader input, EnumSchema type) const { cannam@62: return decode(input, Type(type), Orphanage()).getReader().as(); cannam@62: } cannam@62: cannam@62: // ----------------------------------------------------------------------------- cannam@62: cannam@62: class JsonCodec::HandlerBase { cannam@62: // Internal helper; ignore. cannam@62: public: cannam@62: virtual void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const = 0; cannam@62: virtual Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const; cannam@62: virtual void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: DynamicStruct::Builder output) const; cannam@62: }; cannam@62: cannam@62: template cannam@62: class JsonCodec::Handler: private JsonCodec::HandlerBase { cannam@62: public: cannam@62: virtual void encode(const JsonCodec& codec, ReaderFor input, cannam@62: JsonValue::Builder output) const = 0; cannam@62: virtual Orphan decode(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Orphanage orphanage) const = 0; cannam@62: cannam@62: private: cannam@62: void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const override final { cannam@62: encode(codec, input.as(), output); cannam@62: } cannam@62: Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const override final { cannam@62: return decode(codec, input, orphanage); cannam@62: } cannam@62: friend class JsonCodec; cannam@62: }; cannam@62: cannam@62: template cannam@62: class JsonCodec::Handler: private JsonCodec::HandlerBase { cannam@62: public: cannam@62: virtual void encode(const JsonCodec& codec, ReaderFor input, cannam@62: JsonValue::Builder output) const = 0; cannam@62: virtual void decode(const JsonCodec& codec, JsonValue::Reader input, cannam@62: BuilderFor output) const = 0; cannam@62: virtual Orphan decode(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Orphanage orphanage) const { cannam@62: // If subclass does not override, fall back to regular version. cannam@62: auto result = orphanage.newOrphan(); cannam@62: decode(codec, input, result.get()); cannam@62: return result; cannam@62: } cannam@62: cannam@62: private: cannam@62: void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const override final { cannam@62: encode(codec, input.as(), output); cannam@62: } cannam@62: Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const override final { cannam@62: return decode(codec, input, orphanage); cannam@62: } cannam@62: void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: DynamicStruct::Builder output) const override final { cannam@62: decode(codec, input, output.as()); cannam@62: } cannam@62: friend class JsonCodec; cannam@62: }; cannam@62: cannam@62: template <> cannam@62: class JsonCodec::Handler: private JsonCodec::HandlerBase { cannam@62: // Almost identical to Style::STRUCT except that we pass the struct type to decode(). cannam@62: cannam@62: public: cannam@62: virtual void encode(const JsonCodec& codec, DynamicStruct::Reader input, cannam@62: JsonValue::Builder output) const = 0; cannam@62: virtual void decode(const JsonCodec& codec, JsonValue::Reader input, cannam@62: DynamicStruct::Builder output) const = 0; cannam@62: virtual Orphan decode(const JsonCodec& codec, JsonValue::Reader input, cannam@62: StructSchema type, Orphanage orphanage) const { cannam@62: // If subclass does not override, fall back to regular version. cannam@62: auto result = orphanage.newOrphan(type); cannam@62: decode(codec, input, result.get()); cannam@62: return result; cannam@62: } cannam@62: cannam@62: private: cannam@62: void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const override final { cannam@62: encode(codec, input.as(), output); cannam@62: } cannam@62: Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const override final { cannam@62: return decode(codec, input, type.asStruct(), orphanage); cannam@62: } cannam@62: void decodeStructBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: DynamicStruct::Builder output) const override final { cannam@62: decode(codec, input, output.as()); cannam@62: } cannam@62: friend class JsonCodec; cannam@62: }; cannam@62: cannam@62: template cannam@62: class JsonCodec::Handler: private JsonCodec::HandlerBase { cannam@62: public: cannam@62: virtual void encode(const JsonCodec& codec, T input, JsonValue::Builder output) const = 0; cannam@62: virtual T decode(const JsonCodec& codec, JsonValue::Reader input) const = 0; cannam@62: cannam@62: private: cannam@62: void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const override final { cannam@62: encode(codec, input.as(), output); cannam@62: } cannam@62: Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const override final { cannam@62: return decode(codec, input); cannam@62: } cannam@62: friend class JsonCodec; cannam@62: }; cannam@62: cannam@62: template cannam@62: class JsonCodec::Handler: private JsonCodec::HandlerBase { cannam@62: public: cannam@62: virtual void encode(const JsonCodec& codec, typename T::Client input, cannam@62: JsonValue::Builder output) const = 0; cannam@62: virtual typename T::Client decode(const JsonCodec& codec, JsonValue::Reader input) const = 0; cannam@62: cannam@62: private: cannam@62: void encodeBase(const JsonCodec& codec, DynamicValue::Reader input, cannam@62: JsonValue::Builder output) const override final { cannam@62: encode(codec, input.as(), output); cannam@62: } cannam@62: Orphan decodeBase(const JsonCodec& codec, JsonValue::Reader input, cannam@62: Type type, Orphanage orphanage) const override final { cannam@62: return orphanage.newOrphanCopy(decode(codec, input)); cannam@62: } cannam@62: friend class JsonCodec; cannam@62: }; cannam@62: cannam@62: template cannam@62: inline void JsonCodec::addTypeHandler(Handler& handler) { cannam@62: addTypeHandlerImpl(Type::from(), handler); cannam@62: } cannam@62: inline void JsonCodec::addTypeHandler(Type type, Handler& handler) { cannam@62: addTypeHandlerImpl(type, handler); cannam@62: } cannam@62: inline void JsonCodec::addTypeHandler(EnumSchema type, Handler& handler) { cannam@62: addTypeHandlerImpl(type, handler); cannam@62: } cannam@62: inline void JsonCodec::addTypeHandler(StructSchema type, Handler& handler) { cannam@62: addTypeHandlerImpl(type, handler); cannam@62: } cannam@62: inline void JsonCodec::addTypeHandler(ListSchema type, Handler& handler) { cannam@62: addTypeHandlerImpl(type, handler); cannam@62: } cannam@62: inline void JsonCodec::addTypeHandler(InterfaceSchema type, Handler& handler) { cannam@62: addTypeHandlerImpl(type, handler); cannam@62: } cannam@62: cannam@62: template cannam@62: inline void JsonCodec::addFieldHandler(StructSchema::Field field, Handler& handler) { cannam@62: addFieldHandlerImpl(field, Type::from(), handler); cannam@62: } cannam@62: cannam@62: template <> void JsonCodec::addTypeHandler(Handler& handler) cannam@62: KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; " cannam@62: "try specifying a specific type schema as the first parameter"); cannam@62: template <> void JsonCodec::addTypeHandler(Handler& handler) cannam@62: KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; " cannam@62: "try specifying a specific type schema as the first parameter"); cannam@62: template <> void JsonCodec::addTypeHandler(Handler& handler) cannam@62: KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; " cannam@62: "try specifying a specific type schema as the first parameter"); cannam@62: template <> void JsonCodec::addTypeHandler(Handler& handler) cannam@62: KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; " cannam@62: "try specifying a specific type schema as the first parameter"); cannam@62: template <> void JsonCodec::addTypeHandler(Handler& handler) cannam@62: KJ_UNAVAILABLE("JSON handlers for type sets (e.g. all structs, all lists) not implemented; " cannam@62: "try specifying a specific type schema as the first parameter"); cannam@62: // TODO(someday): Implement support for registering handlers that cover thinsg like "all structs" cannam@62: // or "all lists". Currently you can only target a specific struct or list type. cannam@62: cannam@62: } // namespace capnp cannam@62: cannam@62: #endif // CAPNP_COMPAT_JSON_H_