RTBKit  0.9
Open-source framework to create real-time ad bidding systems.
soa/types/value_description.h
00001 /* value_description.h                                             -*- C++ -*-
00002    Jeremy Barnes, 29 March 2013
00003    Copyright (c) 2013 Datacratic Inc.  All rights reserved.
00004 
00005    Code for description and introspection of values and structures.  Used
00006    to allow for automated formatters and parsers to be built.
00007 */
00008 
00009 #pragma once
00010 
00011 #include <string>
00012 #include <memory>
00013 #include <unordered_map>
00014 #include "jml/arch/exception.h"
00015 #include "jml/arch/demangle.h"
00016 #include "json_parsing.h"
00017 #include "json_printing.h"
00018 #include "value_description_fwd.h"
00019 
00020 namespace Datacratic {
00021 
00022 struct JsonParsingContext;
00023 struct JsonPrintingContext;
00024 struct JSConverters;
00025 
00026 enum class ValueKind : int32_t {
00027     // Atomic, ie all or none is replaced
00028     ATOM,     
00029     INTEGER,
00030     FLOAT,
00031     BOOLEAN,
00032     STRING,
00033     ENUM,
00034 
00035     // Non-atomic, ie part of them can be mutated
00036     OPTIONAL,
00037     ARRAY,
00038     STRUCTURE,
00039     TUPLE,
00040     VARIANT,
00041     MAP,
00042     ANY
00043 };
00044 
00045 std::ostream & operator << (std::ostream & stream, ValueKind kind);
00046 
00047 
00048 /*****************************************************************************/
00049 /* VALUE DESCRIPTION                                                         */
00050 /*****************************************************************************/
00051 
00058 struct ValueDescription {
00059     typedef std::true_type defined;
00060 
00061     ValueDescription(ValueKind kind,
00062                      const std::type_info * type,
00063                      const std::string & typeName = "")
00064         : kind(kind),
00065           type(type),
00066           typeName(typeName.empty() ? ML::demangle(type->name()) : typeName),
00067           jsConverters(nullptr),
00068           jsConvertersInitialized(false)
00069     {
00070     }
00071     
00072     ValueKind kind;
00073     const std::type_info * const type;
00074     const std::string typeName;
00075 
00076     virtual void parseJson(void * val, JsonParsingContext & context) const = 0;
00077     virtual void printJson(const void * val, JsonPrintingContext & context) const = 0;
00078     virtual bool isDefault(const void * val) const = 0;
00079     virtual void setDefault(void * val) const = 0;
00080     virtual void copyValue(const void * from, void * to) const = 0;
00081     virtual void moveValue(void * from, void * to) const = 0;
00082     virtual void swapValues(void * from, void * to) const = 0;
00083     
00084     virtual void * optionalMakeValue(void * val) const
00085     {
00086         throw ML::Exception("type is not optional");
00087     }
00088 
00089     virtual const void * optionalGetValue(const void * val) const
00090     {
00091         throw ML::Exception("type is not optional");
00092     }
00093 
00094     virtual size_t getArrayLength(void * val) const
00095     {
00096         throw ML::Exception("type is not an array");
00097     }
00098 
00099     virtual void * getArrayElement(void * val, uint32_t element) const
00100     {
00101         throw ML::Exception("type is not an array");
00102     }
00103 
00104     virtual const void * getArrayElement(const void * val, uint32_t element) const
00105     {
00106         throw ML::Exception("type is not an array");
00107     }
00108 
00109     virtual void setArrayLength(void * val, size_t newLength) const
00110     {
00111         throw ML::Exception("type is not an array");
00112     }
00113     
00114     virtual const ValueDescription & contained() const
00115     {
00116         throw ML::Exception("type does not contain another");
00117     }
00118 
00119     // Convert from one type to another, making a copy.
00120     // Default will go through a JSON conversion.
00121     virtual void convertAndCopy(const void * from,
00122                                 const ValueDescription & fromDesc,
00123                                 void * to) const;
00124 
00125     struct FieldDescription {
00126         std::string fieldName;
00127         std::string comment;
00128         std::unique_ptr<ValueDescription > description;
00129         int offset;
00130         int fieldNum;
00131     };
00132 
00133     virtual size_t getFieldCount(const void * val) const
00134     {
00135         throw ML::Exception("type doesn't support fields");
00136     }
00137 
00138     virtual const FieldDescription *
00139     hasField(const void * val, const std::string & name) const
00140     {
00141         throw ML::Exception("type doesn't support fields");
00142     }
00143 
00144     virtual void forEachField(const void * val,
00145                               const std::function<void (const FieldDescription &)> & onField) const
00146     {
00147         throw ML::Exception("type doesn't support fields");
00148     }
00149 
00150     virtual const FieldDescription & 
00151     getField(const std::string & field) const
00152     {
00153         throw ML::Exception("type doesn't support fields");
00154     }
00155 
00156     // Storage to cache Javascript converters
00157     mutable JSConverters * jsConverters;
00158     mutable bool jsConvertersInitialized;
00159 };
00160 
00161 void registerValueDescription(const std::type_info & type,
00162                               std::function<ValueDescription * ()>,
00163                               bool isDefault);
00164 
00165 template<typename T>
00166 struct RegisterValueDescription {
00167     RegisterValueDescription()
00168     {
00169         registerValueDescription(typeid(T), [] () { return getDefaultDescription((T*)0); });
00170     }
00171 };
00172 
00173 template<typename T, typename Impl>
00174 struct RegisterValueDescriptionI {
00175     RegisterValueDescriptionI()
00176         : done(false)
00177     {
00178         registerValueDescription(typeid(T), [] () { return new Impl(); }, true);
00179     }
00180 
00181     bool done;
00182 };
00183 
00184 #define REGISTER_VALUE_DESCRIPTION(type)                                \
00185     namespace {                                                         \
00186     static const RegisterValueDescription<type> registerValueDescription#type; \
00187     }
00188 
00189 
00190 /*****************************************************************************/
00191 /* VALUE DESCRIPTION TEMPLATE                                                */
00192 /*****************************************************************************/
00193 
00198 template<typename T>
00199 struct ValueDescriptionT : public ValueDescription {
00200 
00201     ValueDescriptionT(ValueKind kind = ValueKind::ATOM)
00202         : ValueDescription(kind, &typeid(T))
00203     {
00204     }
00205 
00206     virtual void parseJson(void * val, JsonParsingContext & context) const
00207     {
00208         T * val2 = reinterpret_cast<T *>(val);
00209         return parseJsonTyped(val2, context);
00210     }
00211 
00212     virtual void parseJsonTyped(T * val, JsonParsingContext & context) const
00213     {
00214         return parseJson(val, context);
00215     }
00216 
00217     virtual void printJson(const void * val, JsonPrintingContext & context) const
00218     {
00219         const T * val2 = reinterpret_cast<const T *>(val);
00220         return printJsonTyped(val2, context);
00221     }
00222 
00223     virtual void printJsonTyped(const T * val, JsonPrintingContext & context) const
00224     {
00225         return printJson(val, context);
00226     }
00227 
00228     virtual bool isDefault(const void * val) const
00229     {
00230         const T * val2 = reinterpret_cast<const T *>(val);
00231         return isDefaultTyped(val2);
00232     }
00233 
00234     virtual bool isDefaultTyped(const T * val) const
00235     {
00236         return false;
00237     }
00238 
00239     virtual void setDefault(void * val) const
00240     {
00241         T * val2 = reinterpret_cast<T *>(val);
00242         setDefaultTyped(val2);
00243     }
00244 
00245     virtual void setDefaultTyped(T * val) const
00246     {
00247         *val = T();
00248     }
00249 
00250     virtual void copyValue(const void * from, void * to) const
00251     {
00252         auto from2 = reinterpret_cast<const T *>(from);
00253         auto to2 = reinterpret_cast<T *>(to);
00254         if (from2 == to2)
00255             return;
00256         *to2 = *from2;
00257     }
00258 
00259     virtual void moveValue(void * from, void * to) const
00260     {
00261         auto from2 = reinterpret_cast<T *>(from);
00262         auto to2 = reinterpret_cast<T *>(to);
00263         if (from2 == to2)
00264             return;
00265         *to2 = std::move(*from2);
00266     }
00267 
00268     virtual void swapValues(void * from, void * to) const
00269     {
00270         using std::swap;
00271         auto from2 = reinterpret_cast<T *>(from);
00272         auto to2 = reinterpret_cast<T *>(to);
00273         if (from2 == to2)
00274             return;
00275         std::swap(*from2, *to2);
00276     }
00277 
00278     virtual void * optionalMakeValue(void * val) const
00279     {
00280         T * val2 = reinterpret_cast<T *>(val);
00281         return optionalMakeValueTyped(val2);
00282     }
00283 
00284     virtual void * optionalMakeValueTyped(T * val) const
00285     {
00286         throw ML::Exception("type is not optional");
00287     }
00288 
00289     virtual const void * optionalGetValue(const void * val) const
00290     {
00291         const T * val2 = reinterpret_cast<const T *>(val);
00292         return optionalGetValueTyped(val2);
00293     }
00294 
00295     virtual const void * optionalGetValueTyped(const T * val) const
00296     {
00297         throw ML::Exception("type is not optional");
00298     }
00299 };
00300 
00301 template<typename T>
00302 ValueDescriptionT<T> *
00303 getDefaultDescription(T * = 0,
00304                       typename DefaultDescription<T>::defined * = 0)
00305 {
00306     return new DefaultDescription<T>();
00307 }
00308 
00309 template<typename T, ValueKind kind = ValueKind::ATOM,
00310          typename Impl = DefaultDescription<T> >
00311 struct ValueDescriptionI : public ValueDescriptionT<T> {
00312 
00313     static RegisterValueDescriptionI<T, Impl> regme;
00314 
00315     ValueDescriptionI()
00316         : ValueDescriptionT<T>(kind)
00317     {
00318         regme.done = true;
00319     }
00320 };
00321 
00322 template<typename T, ValueKind kind, typename Impl>
00323 RegisterValueDescriptionI<T, Impl>
00324 ValueDescriptionI<T, kind, Impl>::
00325 regme;
00326 
00327 template<class Struct>
00328 struct StructureDescription;
00329 
00330 inline void * addOffset(void * base, ssize_t offset)
00331 {
00332     char * c = reinterpret_cast<char *>(base);
00333     return c + offset;
00334 }
00335 
00336 inline const void * addOffset(const void * base, ssize_t offset)
00337 {
00338     const char * c = reinterpret_cast<const char *>(base);
00339     return c + offset;
00340 }
00341 
00342 
00343 /*****************************************************************************/
00344 /* STRUCTURE DESCRIPTION BASE                                                */
00345 /*****************************************************************************/
00346 
00349 struct StructureDescriptionBase {
00350 
00351     StructureDescriptionBase(const std::type_info * type,
00352                              const std::string & typeName = "",
00353                              bool nullAccepted = false)
00354         : type(type),
00355           typeName(typeName.empty() ? ML::demangle(type->name()) : typeName),
00356           nullAccepted(nullAccepted)
00357     {
00358     }
00359 
00360     const std::type_info * const type;
00361     const std::string typeName;
00362     bool nullAccepted;
00363 
00364     typedef ValueDescription::FieldDescription FieldDescription;
00365 
00366     // Comparison object to allow const char * objects to be looked up
00367     // in the map and so for comparisons to be done with no memory
00368     // allocations.
00369     struct StrCompare {
00370         inline bool operator () (const char * s1, const char * s2) const
00371         {
00372             char c1 = *s1++, c2 = *s2++;
00373 
00374             if (c1 < c2) return true;
00375             if (c1 > c2) return false;
00376             if (c1 == 0) return false;
00377 
00378             c1 = *s1++; c2 = *s2++;
00379             
00380             if (c1 < c2) return true;
00381             if (c1 > c2) return false;
00382             if (c1 == 0) return false;
00383 
00384             return strcmp(s1, s2) < 0;
00385         }
00386 
00387     };
00388 
00389     typedef std::map<const char *, FieldDescription, StrCompare> Fields;
00390     Fields fields;
00391 
00392     std::vector<std::string> fieldNames;
00393 
00394     std::vector<Fields::const_iterator> orderedFields;
00395 
00396     virtual void parseJson(void * output, JsonParsingContext & context) const
00397     {
00398         if (!onEntry(output, context)) return;
00399 
00400         if (nullAccepted && context.isNull()) {
00401             context.expectNull();
00402             return;
00403         }
00404         
00405         if (!context.isObject())
00406             context.exception("expected structure of type " + typeName);
00407 
00408         auto onMember = [&] ()
00409             {
00410                 //using namespace std;
00411                 //cerr << "got field " << context.printPath() << endl;
00412 
00413                 auto n = context.fieldNamePtr();
00414                 auto it = fields.find(n);
00415                 if (it == fields.end()) {
00416                     context.onUnknownField();
00417                 }
00418                 else {
00419                     it->second.description
00420                     ->parseJson(addOffset(output, it->second.offset),
00421                                 context);
00422                 }
00423             };
00424         
00425         context.forEachMember(onMember);
00426 
00427         onExit(output, context);
00428     }
00429 
00430     virtual void printJson(const void * input, JsonPrintingContext & context) const
00431     {
00432         context.startObject();
00433 
00434         for (const auto & it: orderedFields) {
00435             auto & fd = it->second;
00436 
00437             auto mbr = addOffset(input, fd.offset);
00438             if (fd.description->isDefault(mbr))
00439                 continue;
00440             context.startMember(it->first);
00441             fd.description->printJson(mbr, context);
00442         }
00443         
00444         context.endObject();
00445     }
00446 
00447     virtual bool onEntry(void * output, JsonParsingContext & context) const = 0;
00448     virtual void onExit(void * output, JsonParsingContext & context) const = 0;
00449 };
00450 
00451 
00452 /*****************************************************************************/
00453 /* STRUCTURE DESCRIPTION                                                     */
00454 /*****************************************************************************/
00455 
00456 template<class Struct>
00457 struct StructureDescription
00458     :  public ValueDescriptionI<Struct, ValueKind::STRUCTURE,
00459                                 StructureDescription<Struct> >,
00460        public StructureDescriptionBase {
00461 
00462     StructureDescription(bool nullAccepted = false)
00463         : StructureDescriptionBase(&typeid(Struct), "", nullAccepted)
00464     {
00465     }
00466 
00468     std::function<bool (Struct *, JsonParsingContext & context)> onEntryHandler;
00469 
00471     std::function<void (Struct *, JsonParsingContext & context)> onUnknownField;
00472 
00473     virtual bool onEntry(void * output, JsonParsingContext & context) const
00474     {
00475         if (onEntryHandler) {
00476             if (!onEntryHandler((Struct *)output, context))
00477                 return false;
00478         }
00479         
00480         if (onUnknownField)
00481             context.onUnknownFieldHandlers.push_back([=,&context] () { this->onUnknownField((Struct *)output, context); });
00482 
00483         return true;
00484     }
00485     
00486     virtual void onExit(void * output, JsonParsingContext & context) const
00487     {
00488         if (onUnknownField)
00489             context.onUnknownFieldHandlers.pop_back();
00490     }
00491 
00492     template<typename V, typename Base>
00493     void addField(std::string name,
00494                   V Base::* field,
00495                   std::string comment,
00496                   ValueDescriptionT<V> * description
00497                   = getDefaultDescription((V *)0))
00498     {
00499         if (fields.count(name.c_str()))
00500             throw ML::Exception("field '" + name + "' added twice");
00501 
00502         fieldNames.push_back(name);
00503         const char * fieldName = fieldNames.back().c_str();
00504         
00505         auto it = fields.insert
00506             (Fields::value_type(fieldName, std::move(FieldDescription())))
00507             .first;
00508         
00509         FieldDescription & fd = it->second;
00510         fd.fieldName = fieldName;
00511         fd.comment = comment;
00512         fd.description.reset(description);
00513         Struct * p = nullptr;
00514         fd.offset = (size_t)&(p->*field);
00515         fd.fieldNum = fields.size() - 1;
00516         orderedFields.push_back(it);
00517         //using namespace std;
00518         //cerr << "offset = " << fd.offset << endl;
00519     }
00520 
00521     template<typename V>
00522     void addParent(ValueDescriptionT<V> * description_
00523                    = getDefaultDescription((V *)0))
00524     {
00525         StructureDescription<V> * desc2
00526             = dynamic_cast<StructureDescription<V> *>(description_);
00527         if (!desc2) {
00528             delete description_;
00529             throw ML::Exception("parent description is not a structure");
00530         }
00531 
00532         std::unique_ptr<StructureDescription<V> > description(desc2);
00533 
00534         Struct * p = nullptr;
00535         V * p2 = static_cast<V *>(p);
00536 
00537         size_t ofs = (size_t)p2;
00538 
00539         for (auto & oit: description->orderedFields) {
00540             FieldDescription & ofd = const_cast<FieldDescription &>(oit->second);
00541             const std::string & name = ofd.fieldName;
00542 
00543             fieldNames.push_back(name);
00544             const char * fieldName = fieldNames.back().c_str();
00545 
00546             auto it = fields.insert(Fields::value_type(fieldName, std::move(FieldDescription()))).first;
00547             FieldDescription & fd = it->second;
00548             fd.fieldName = fieldName;
00549             fd.comment = ofd.comment;
00550             fd.description = std::move(ofd.description);
00551             
00552             fd.offset = ofd.offset + ofs;
00553             fd.fieldNum = fields.size() - 1;
00554             orderedFields.push_back(it);
00555         }
00556     }
00557 
00558     virtual size_t getFieldCount(const void * val) const
00559     {
00560         return fields.size();
00561     }
00562 
00563     virtual const FieldDescription *
00564     hasField(const void * val, const std::string & field) const
00565     {
00566         auto it = fields.find(field.c_str());
00567         if (it != fields.end())
00568             return &it->second;
00569         return nullptr;
00570     }
00571 
00572     virtual void forEachField(const void * val,
00573                               const std::function<void (const FieldDescription &)> & onField) const
00574     {
00575         for (auto f: orderedFields) {
00576             onField(f->second);
00577         }
00578     }
00579 
00580     virtual const FieldDescription & 
00581     getField(const std::string & field) const
00582     {
00583         auto it = fields.find(field.c_str());
00584         if (it != fields.end())
00585             return it->second;
00586         throw ML::Exception("structure has no field " + field);
00587     }
00588 
00589     virtual void parseJson(void * val, JsonParsingContext & context) const
00590     {
00591         return StructureDescriptionBase::parseJson(val, context);
00592     }
00593 
00594     virtual void printJson(const void * val, JsonPrintingContext & context) const
00595     {
00596         return StructureDescriptionBase::printJson(val, context);
00597     }
00598 };
00599 
00600 template<typename Enum>
00601 struct EnumDescription: public ValueDescriptionT<Enum> {
00602 
00603     struct Value {
00604         int value;
00605         std::string name;
00606     };
00607 
00608     std::unordered_map<std::string, int> parse;
00609     std::unordered_map<int, Value> print;
00610 };
00611 
00612 template<typename T>
00613 struct ListDescriptionBase {
00614 
00615     ListDescriptionBase(ValueDescriptionT<T> * inner = getDefaultDescription((T *)0))
00616         : inner(inner)
00617     {
00618     }
00619 
00620     std::unique_ptr<ValueDescriptionT<T> > inner;
00621 
00622     template<typename List>
00623     void parseJsonTypedList(List * val, JsonParsingContext & context) const
00624     {
00625         val->clear();
00626 
00627         if (!context.isArray())
00628             context.exception("expected array of " + inner->typeName);
00629         
00630         auto onElement = [&] ()
00631             {
00632                 T el;
00633                 inner->parseJsonTyped(&el, context);
00634                 val->emplace_back(std::move(el));
00635             };
00636         
00637         context.forEachElement(onElement);
00638     }
00639 
00640     template<typename List>
00641     void printJsonTypedList(const List * val, JsonPrintingContext & context) const
00642     {
00643         context.startArray(val->size());
00644 
00645         for (unsigned i = 0;  i < val->size();  ++i) {
00646             context.newArrayElement();
00647             inner->printJsonTyped(&(*val)[i], context);
00648         }
00649         
00650         context.endArray();
00651     }
00652 };
00653 
00654 template<typename T>
00655 struct DefaultDescription<std::vector<T> >
00656     : public ValueDescriptionI<std::vector<T>, ValueKind::ARRAY>,
00657       public ListDescriptionBase<T> {
00658 
00659     DefaultDescription(ValueDescriptionT<T> * inner
00660                       = getDefaultDescription((T *)0))
00661         : ListDescriptionBase<T>(inner)
00662     {
00663     }
00664 
00665     virtual void parseJson(void * val, JsonParsingContext & context) const
00666     {
00667         std::vector<T> * val2 = reinterpret_cast<std::vector<T> *>(val);
00668         return parseJsonTyped(val2, context);
00669     }
00670 
00671     virtual void parseJsonTyped(std::vector<T> * val, JsonParsingContext & context) const
00672     {
00673         this->parseJsonTypedList(val, context);
00674     }
00675 
00676     virtual void printJson(const void * val, JsonPrintingContext & context) const
00677     {
00678         const std::vector<T> * val2 = reinterpret_cast<const std::vector<T> *>(val);
00679         return printJsonTyped(val2, context);
00680     }
00681 
00682     virtual void printJsonTyped(const std::vector<T> * val, JsonPrintingContext & context) const
00683     {
00684         this->printJsonTypedList(val, context);
00685     }
00686 
00687     virtual bool isDefault(const void * val) const
00688     {
00689         const std::vector<T> * val2 = reinterpret_cast<const std::vector<T> *>(val);
00690         return isDefaultTyped(val2);
00691     }
00692 
00693     virtual bool isDefaultTyped(const std::vector<T> * val) const
00694     {
00695         return val->empty();
00696     }
00697 
00698     virtual size_t getArrayLength(void * val) const
00699     {
00700         const std::vector<T> * val2 = reinterpret_cast<const std::vector<T> *>(val);
00701         return val2->size();
00702     }
00703 
00704     virtual void * getArrayElement(void * val, uint32_t element) const
00705     {
00706         std::vector<T> * val2 = reinterpret_cast<std::vector<T> *>(val);
00707         return &val2->at(element);
00708     }
00709 
00710     virtual const void * getArrayElement(const void * val, uint32_t element) const
00711     {
00712         const std::vector<T> * val2 = reinterpret_cast<const std::vector<T> *>(val);
00713         return &val2->at(element);
00714     }
00715 
00716     virtual void setArrayLength(void * val, size_t newLength) const
00717     {
00718         std::vector<T> * val2 = reinterpret_cast<std::vector<T> *>(val);
00719         val2->resize(newLength);
00720     }
00721     
00722     virtual const ValueDescription & contained() const
00723     {
00724         return *this->inner;
00725     }
00726 };
00727 
00728 
00729 // Template set for which hasToJson<T>::value is true if and only if it has a function
00730 // Json::Value T::toJson() const
00731 template<typename T, typename Enable = void>
00732 struct hasToJson {
00733     enum { value = false };
00734 };
00735 
00736 template<typename T>
00737 struct hasToJson<T, typename std::enable_if<std::is_convertible<Json::Value, decltype(std::declval<const T>().toJson())>::value>::type> {
00738     enum { value = true };
00739 };
00740 
00741 // Template set for which hasFromJson<T>::value is true if and only if it has a function
00742 // static T T::fromJson(Json::Value)
00743 template<typename T, typename Enable = void>
00744 struct hasFromJson {
00745     enum { value = false };
00746 };
00747 
00748 template<typename T>
00749 struct hasFromJson<T, typename std::enable_if<std::is_convertible<T, decltype(T::fromJson(std::declval<Json::Value>()))>::value>::type> {
00750     enum { value = true };
00751 };
00752 
00753 // jsonDecode implementation for any type which:
00754 // 1) has a default description;
00755 // 2) does NOT have a fromJson() function (there is a simpler overload for this case)
00756 template<typename T>
00757 T jsonDecode(const Json::Value & json, T * = 0,
00758              decltype(getDefaultDescription((T *)0)) * = 0,
00759              typename std::enable_if<!hasFromJson<T>::value>::type * = 0)
00760 {
00761     T result;
00762 
00763     static std::unique_ptr<ValueDescription> desc
00764         (getDefaultDescription((T *)0));
00765     StructuredJsonParsingContext context(json);
00766     desc->parseJson(&result, context);
00767     return result;
00768 }
00769 
00770 // jsonEncode implementation for any type which:
00771 // 1) has a default description;
00772 // 2) does NOT have a toJson() function (there is a simpler overload for this case)
00773 template<typename T>
00774 Json::Value jsonEncode(const T & obj,
00775                        decltype(getDefaultDescription((T *)0)) * = 0,
00776                        typename std::enable_if<!hasToJson<T>::value>::type * = 0)
00777 {
00778     static std::unique_ptr<ValueDescription> desc
00779         (getDefaultDescription((T *)0));
00780     StructuredJsonPrintingContext context;
00781     desc->printJson(&obj, context);
00782     return std::move(context.output);
00783 }
00784 
00785 } // namespace Datacratic
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator