diff --git a/documentation/Doxyfile b/documentation/Doxyfile index 5931ef9..e11389c 100644 --- a/documentation/Doxyfile +++ b/documentation/Doxyfile @@ -767,6 +767,7 @@ INPUT = ../src/pv \ ./mainpage.h \ ../src/copy/pv \ ../src/misc/pv \ + ../src/json/pv \ ./release_notes.h # This tag can be used to specify the character encoding of the source files diff --git a/src/Makefile b/src/Makefile index 730ec2f..e3e2a4d 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,6 +11,7 @@ include $(PVDATA_SRC)/factory/Makefile include $(PVDATA_SRC)/property/Makefile include $(PVDATA_SRC)/copy/Makefile include $(PVDATA_SRC)/pvMisc/Makefile +include $(PVDATA_SRC)/json/Makefile LIBRARY = pvData diff --git a/src/json/Makefile b/src/json/Makefile new file mode 100644 index 0000000..0baca83 --- /dev/null +++ b/src/json/Makefile @@ -0,0 +1,10 @@ +# This is a Makefile fragment, see ../Makefile + +SRC_DIRS += $(PVDATA_SRC)/json + +INC += pv/json.h + +LIBSRCS += parsehelper.cpp +LIBSRCS += parseany.cpp +LIBSRCS += parseinto.cpp +LIBSRCS += print.cpp diff --git a/src/json/parseany.cpp b/src/json/parseany.cpp new file mode 100644 index 0000000..96b2770 --- /dev/null +++ b/src/json/parseany.cpp @@ -0,0 +1,276 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include + +#include +#include +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#include + +#define epicsExportSharedSymbols +#include "pv/json.h" + +namespace pvd = epics::pvData; + +namespace { + +struct context { + + unsigned depth; + + enum state_t { + Undefined, + Key, + Array, + } state; + + pvd::shared_vector arr; + + pvd::ValueBuilder root, + *cur; + + std::string msg, + key; + + context() :depth(0u), state(Undefined), cur(&root) {} +}; + +#define TRY context *self = (context*)ctx; try + +#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; } + +int jtree_null(void * ctx) +{ + TRY { + self->msg = "NULL value not permitted"; + return 0; + }CATCH() +} + +int jtree_boolean(void * ctx, int boolVal) +{ + TRY { + if(self->depth==0) throw std::runtime_error("Bare value not supported"); + switch(self->state) { + case context::Key: + self->cur = &self->cur->add(self->key, boolVal); + self->key.clear(); + self->state = context::Undefined; + break; + case context::Array: + { + if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvBoolean) + throw std::runtime_error("Mixed type array not supported"); + pvd::shared_vector arr(pvd::static_shared_vector_cast(self->arr)); + arr.push_back(boolVal); + self->arr = pvd::static_shared_vector_cast(arr); + break; + } + default: + throw std::logic_error("boolean in bad state"); + } + return 1; + }CATCH() +} + +int jtree_integer(void * ctx, long integerVal) +{ + TRY { + if(self->depth==0) throw std::runtime_error("Bare value not supported"); + switch(self->state) { + case context::Key: + self->cur = &self->cur->add(self->key, integerVal); + self->key.clear(); + self->state = context::Undefined; + break; + case context::Array: + { + if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvLong) + throw std::runtime_error("Mixed type array not supported"); + pvd::shared_vector arr(pvd::static_shared_vector_cast(self->arr)); + arr.push_back(integerVal); + self->arr = pvd::static_shared_vector_cast(arr); + break; + } + default: + throw std::logic_error("int64 in bad state"); + } + return 1; + }CATCH() +} + +int jtree_double(void * ctx, double doubleVal) +{ + TRY { + if(self->depth==0) throw std::runtime_error("Bare value not supported"); + switch(self->state) { + case context::Key: + self->cur = &self->cur->add(self->key, doubleVal); + self->key.clear(); + self->state = context::Undefined; + break; + case context::Array: + { + if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvDouble) + throw std::runtime_error("Mixed type array not supported"); + pvd::shared_vector arr(pvd::static_shared_vector_cast(self->arr)); + arr.push_back(doubleVal); + self->arr = pvd::static_shared_vector_cast(arr); + break; + } + default: + throw std::logic_error("double in bad state"); + } + return 1; + }CATCH() +} + +int jtree_string(void * ctx, const unsigned char * stringVal, + unsigned int stringLen) +{ + TRY { + if(self->depth==0) throw std::runtime_error("Bare value not supported"); + std::string sval((const char*)stringVal, stringLen); + switch(self->state) { + case context::Key: + self->cur = &self->cur->add(self->key, sval); + self->key.clear(); + self->state = context::Undefined; + break; + case context::Array: + { + if(self->arr.size()>0 && self->arr.original_type()!=pvd::pvString) + throw std::runtime_error("Mixed type array not supported"); + pvd::shared_vector arr(pvd::static_shared_vector_cast(self->arr)); + arr.push_back(sval); + self->arr = pvd::static_shared_vector_cast(arr); + break; + } + default: + throw std::logic_error("double in bad state"); + } + return 1; + }CATCH() +} + +int jtree_start_map(void * ctx) +{ + TRY { + if(self->depth>0) { + if(self->key.empty()) + throw std::logic_error("anonymous dict not top level?"); + self->cur = &self->cur->addNested(self->key); + self->key.clear(); + } + self->depth++; + return 1; + }CATCH() +} + +int jtree_map_key(void * ctx, const unsigned char * key, + unsigned int stringLen) +{ + TRY { + if(!self->key.empty()) + throw std::logic_error("double key?"); + if(stringLen==0) + throw std::runtime_error("empty key not allowed"); + self->key = std::string((const char*)key, stringLen); + self->state = context::Key; + return 1; + }CATCH() +} + +int jtree_end_map(void * ctx) +{ + TRY { + if(self->depth>1) + self->cur = &self->cur->endNested(); + else if(self->depth==0) + throw std::logic_error("Unbalenced dict"); + self->depth--; + return 1; + }CATCH() +} + +int jtree_start_array(void * ctx) +{ + TRY { + if(self->depth==0) throw std::runtime_error("Bare array not supported"); + if(self->state!=context::Key) + throw std::logic_error("bare array not supported"); + self->state = context::Array; + return 1; + }CATCH() +} +int jtree_end_array(void * ctx) +{ + TRY { + if(self->state!=context::Array) + throw std::logic_error("Bad array parse"); + self->cur = &self->cur->add(self->key, pvd::freeze(self->arr)); + self->key.clear(); + self->state = context::Undefined; + return 1; + }CATCH() +} + + +yajl_callbacks jtree_cbs = { + &jtree_null, + &jtree_boolean, + &jtree_integer, + &jtree_double, + NULL, // number + &jtree_string, + &jtree_start_map, + &jtree_map_key, + &jtree_end_map, + &jtree_start_array, + &jtree_end_array, +}; + +struct handler { + yajl_handle handle; + handler(yajl_handle handle) :handle(handle) + { + if(!handle) + throw std::runtime_error("Failed to allocate yajl handle"); + } + ~handler() { + yajl_free(handle); + } + operator yajl_handle() { return handle; } +}; + +} // namespace + +namespace epics{namespace pvData{ + +epics::pvData::PVStructure::shared_pointer +parseJSON(std::istream& strm) +{ + yajl_parser_config conf = { + .allowComments = 1, + .checkUTF8 = 1, + }; + + context ctxt; + + handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt)); + + if(!yajl_parse_helper(strm, handle, conf)) + throw std::runtime_error(ctxt.msg); + + return ctxt.cur->buildPVStructure(); +} + +}} // namespace epics::pvData + +#endif // EPICS_VERSION_INT diff --git a/src/json/parsehelper.cpp b/src/json/parsehelper.cpp new file mode 100644 index 0000000..a694d5f --- /dev/null +++ b/src/json/parsehelper.cpp @@ -0,0 +1,104 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include +#include + +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#define epicsExportSharedSymbols +#include "pv/json.h" + +namespace { + +void check_trailing(const std::string& line, bool commentok) +{ + size_t idx = line.find_first_not_of(" \t\n\r"); + if(idx==line.npos) return; + // TODO: detect the end of potentially multi-line comments... + // for now trailing comments not allowed + throw std::runtime_error("Trailing junk"); +} + +} // namespace + +namespace epics{namespace pvData{ + +bool yajl_parse_helper(std::istream& src, + yajl_handle handle, + const yajl_parser_config& config) +{ + unsigned linenum=0; + bool done = false; + + std::string line; + while(std::getline(src, line)) { + linenum++; + + if(done) { + check_trailing(line, config.allowComments); + continue; + } + + yajl_status sts = yajl_parse(handle, (const unsigned char*)line.c_str(), line.size()); + + switch(sts) { + case yajl_status_ok: { + size_t consumed = yajl_get_bytes_consumed(handle); + if(consumed +#include + +#include +#include +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#include + +#define epicsExportSharedSymbols +#include "pv/json.h" + +namespace pvd = epics::pvData; + +namespace { +struct context { + + std::string msg; + + typedef std::vector stack_t; + stack_t stack; + + context(const pvd::PVFieldPtr& root) + { + stack.push_back(root); + } +}; + +#define TRY context *self = (context*)ctx; assert(!self->stack.empty()); try + +#define CATCH() catch(std::exception& e) { self->msg = e.what(); return 0; } + +int jtree_null(void * ctx) +{ + TRY { + self->msg = "NULL value not permitted"; + return 0; + }CATCH() +} + +template +void valueAssign(context *self, typename PVD::value_type val) +{ + pvd::Type type(self->stack.back()->getField()->getType()); + if(type==pvd::scalar) { + pvd::PVScalar* fld(static_cast(self->stack.back().get())); + if(!fld) + throw std::invalid_argument("Not a scalar field"); + fld->putFrom(val); + self->stack.pop_back(); + // structure back at the top of the stack + + } else if(type==pvd::scalarArray) { + pvd::PVScalarArray* fld(static_cast(self->stack.back().get())); + + PVD* arrfld(dynamic_cast(fld)); + if(!arrfld) + throw std::invalid_argument("wrong type for scalar array"); + + typename PVD::const_svector carr; + arrfld->swap(carr); + + typename PVD::svector arr(pvd::thaw(carr)); + + arr.push_back(val); + + arrfld->replace(pvd::freeze(arr)); + + // leave array field at top of stack + } else { + throw std::invalid_argument("Can't assign value"); + } +} + +int jtree_boolean(void * ctx, int boolVal) +{ + TRY { + valueAssign(self, !!boolVal); + return 1; + }CATCH() +} + +int jtree_integer(void * ctx, long integerVal) +{ + TRY { + valueAssign(self, integerVal); + return 1; + }CATCH() +} + +int jtree_double(void * ctx, double doubleVal) +{ + TRY { + valueAssign(self, doubleVal); + return 1; + }CATCH() +} + +int jtree_string(void * ctx, const unsigned char * stringVal, + unsigned int stringLen) +{ + TRY { + std::string val((const char*)stringVal, stringLen); + valueAssign(self, val); + return 1; + }CATCH() +} + +int jtree_start_map(void * ctx) +{ + TRY { + pvd::PVFieldPtr& back(self->stack.back()); + pvd::Type type = back->getField()->getType(); + if(type==pvd::structure) { + // will fill in + } else if(type==pvd::structureArray) { + // starting new element in structure array + pvd::PVStructureArrayPtr sarr(std::tr1::static_pointer_cast(back)); + + pvd::PVStructurePtr elem(pvd::getPVDataCreate()->createPVStructure(sarr->getStructureArray()->getStructure())); + + self->stack.push_back(elem); + } else { + throw std::runtime_error("Can't map (sub)structure"); + } + + assert(self->stack.back()->getField()->getType()==pvd::structure); + return 1; + }CATCH() +} + +int jtree_map_key(void * ctx, const unsigned char * key, + unsigned int stringLen) +{ + TRY { + std::string name((const char*)key, stringLen); + + // start_map() ensures we have a structure at the top of the stack + pvd::PVStructure *fld = static_cast(self->stack.back().get()); + + try { + self->stack.push_back(fld->getSubFieldT(name)); + }catch(std::runtime_error& e){ + std::ostringstream strm; + strm<<"At "<getFullName()<<" : "<stack.back()->getField()->getType()==pvd::structure); + + pvd::PVStructurePtr elem(std::tr1::static_pointer_cast(self->stack.back())); + self->stack.pop_back(); + + if(!self->stack.empty() && self->stack.back()->getField()->getType()==pvd::structureArray) { + // append element to struct array + pvd::PVStructureArray *sarr = static_cast(self->stack.back().get()); + + pvd::PVStructureArray::const_svector cval; + sarr->swap(cval); + + pvd::PVStructureArray::svector val(pvd::thaw(cval)); + + val.push_back(elem); + + sarr->replace(pvd::freeze(val)); + } + + return 1; + }CATCH() +} + +int jtree_start_array(void * ctx) +{ + TRY { + pvd::PVFieldPtr& back(self->stack.back()); + pvd::Type type = back->getField()->getType(); + if(type!=pvd::structureArray && type!=pvd::scalarArray) + throw std::runtime_error("Can't assign array"); + + return 1; + }CATCH() +} +int jtree_end_array(void * ctx) +{ + TRY { + self->stack.pop_back(); + return 1; + }CATCH() +} + + +yajl_callbacks jtree_cbs = { + &jtree_null, + &jtree_boolean, + &jtree_integer, + &jtree_double, + NULL, // number + &jtree_string, + &jtree_start_map, + &jtree_map_key, + &jtree_end_map, + &jtree_start_array, + &jtree_end_array, +}; + +struct handler { + yajl_handle handle; + handler(yajl_handle handle) :handle(handle) + { + if(!handle) + throw std::runtime_error("Failed to allocate yajl handle"); + } + ~handler() { + yajl_free(handle); + } + operator yajl_handle() { return handle; } +}; + +} // namespace + +namespace epics{namespace pvData{ + +epicsShareFunc +void parseJSON(std::istream& strm, + const PVField::shared_pointer& dest) +{ + yajl_parser_config conf = { + .allowComments = 1, + .checkUTF8 = 1, + }; + + context ctxt(dest); + + handler handle(yajl_alloc(&jtree_cbs, &conf, NULL, &ctxt)); + + if(!yajl_parse_helper(strm, handle, conf)) + throw std::runtime_error(ctxt.msg); + + if(!ctxt.stack.empty()) + throw std::logic_error("field stack not empty"); +} + +}} // namespace epics::pvData + +#endif // EPICS_VERSION_INT + diff --git a/src/json/print.cpp b/src/json/print.cpp new file mode 100644 index 0000000..9c7e706 --- /dev/null +++ b/src/json/print.cpp @@ -0,0 +1,160 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include +#include + +#include +#include +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#include + +#define epicsExportSharedSymbols +#include "pv/json.h" + +namespace pvd = epics::pvData; + +namespace { + +struct args { + std::ostream& strm; + const pvd::JSONPrintOptions& opts; + + unsigned indent; + + args(std::ostream& strm, + const pvd::JSONPrintOptions& opts) + :strm(strm) + ,opts(opts) + ,indent(opts.indent) + {} + + void doIntent() { + if(!opts.multiLine) return; + strm.put('\n'); + unsigned i=indent; + while(i--) strm.put(' '); + } +}; + +void show_field(args& A, const pvd::PVField* fld); + +void show_struct(args& A, const pvd::PVStructure* fld) +{ + const pvd::StructureConstPtr& type = fld->getStructure(); + const pvd::PVFieldPtrArray& children = fld->getPVFields(); + + const pvd::StringArray& names = type->getFieldNames(); + + A.strm.put('{'); + A.indent++; + + for(size_t i=0, N=names.size(); igetField()->getType()) + { + case pvd::scalar: + { + const pvd::PVScalar *scalar=static_cast(fld); + if(scalar->getScalar()->getScalarType()==pvd::pvString) { + A.strm<<'\"'<getAs()<<'\"'; + } else { + A.strm<getAs(); + } + } + break; + case pvd::scalarArray: + { + const pvd::PVScalarArray *scalar=static_cast(fld); + const bool isstring = scalar->getScalarArray()->getElementType()==pvd::pvString; + + pvd::shared_vector arr; + scalar->getAs(arr); + + pvd::shared_vector sarr(pvd::shared_vector_convert(arr)); + + A.strm.put('['); + for(size_t i=0, N=sarr.size(); i(fld)); + break; + case pvd::structureArray: + { + pvd::PVStructureArray::const_svector arr(static_cast(fld)->view()); + A.strm.put('['); + A.indent++; + + for(size_t i=0, N=arr.size(); i +#include +#include +#include + +#include +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#ifdef epicsExportSharedSymbols +# define pvjson_epicsExportSharedSymbols +# undef epicsExportSharedSymbols +#endif + +#include + +#ifdef pvjson_epicsExportSharedSymbols +# define epicsExportSharedSymbols +# include "shareLib.h" +#endif + +#include + +namespace epics{namespace pvData{ + +/** @defgroup pvjson JSON print/parse + * + * Printing PVField as JSON and parsing JSON into PVField. + * + * @{ + */ + +//! Options used during printing +struct epicsShareClass JSONPrintOptions +{ + bool multiLine; //!< include new lines + bool ignoreUnprintable;//!< ignore union/union array when encountered + unsigned indent; //!< Initial indentation (# of spaces) + JSONPrintOptions(); +}; + +/** Print PVStructure as JSON + * + * Restrictions: + * + * - No support for union or array of union + */ +epicsShareFunc +void printJSON(std::ostream& strm, + const PVField::const_shared_pointer& val, + const JSONPrintOptions& opts = JSONPrintOptions()); + +/** Parse JSON text into a PVStructure + * + * Restrictions: + * + * - Top level must be {} dict/object + * - field values must be number, string, array, or dict/object + * - array values must be number or string + */ +epicsShareFunc +PVStructure::shared_pointer parseJSON(std::istream& strm); + +/** Parse JSON and store into the provided PVStructure. + * + * Restrictions: + * + * - Union or array of union not permitted + */ +epicsShareFunc +void parseJSON(std::istream& strm, + const PVField::shared_pointer& dest); + + +/** Wrapper around yajl_parse() + * + * Parse entire input stream. + * Errors if extranious non-whitespace found after the point were parsing completes. + * + * @param src The stream from which input charactors are read + * @param handle A parser handle previously allocated with yajl_alloc(). Not free'd on success or failure. + * @param config The same configuration passed to yajl_alloc(). Used to decide if trailing comments are allowed + * + * @returns true if parsing completes successfully. false if parsing cancelled by callback. throws other errors + */ +epicsShareFunc +bool yajl_parse_helper(std::istream& src, + yajl_handle handle, + const yajl_parser_config& config); + +/** @} */ + +}} // namespace epics::pvData + +#else +# error JSON parser requires EPICS Base >= 3.15.0.1 +#endif // EPICS_VERSION_INT + +#endif // PV_JSON_H diff --git a/src/misc/pv/pvUnitTest.h b/src/misc/pv/pvUnitTest.h index daf994b..8437f4e 100644 --- a/src/misc/pv/pvUnitTest.h +++ b/src/misc/pv/pvUnitTest.h @@ -113,6 +113,8 @@ inline testPassx testEqualx(const char *nLHS, const char *nRHS, LHS l, RHS r) */ #define testTrue(B) ::detail::testPassx(!!(B))<<#B +#define testThrows(EXC, CODE) try{ CODE; testFail("unexpected success of " #CODE); }catch(EXC& e){testPass("catch expected exception: %s", e.what());} + /** Compare value of PVStructure field * @code diff --git a/src/pv/pvdVersion.h b/src/pv/pvdVersion.h index 5ef20ca..ceaaedf 100644 --- a/src/pv/pvdVersion.h +++ b/src/pv/pvdVersion.h @@ -15,6 +15,10 @@ # define VERSION_INT(V,R,M,P) ( ((V)<<24) | ((R)<<16) | ((M)<<8) | (P)) #endif +#ifndef EPICS_VERSION_INT +# define EPICS_VERSION_INT VERSION_INT(EPICS_VERSION, EPICS_REVISION, EPICS_MODIFICATION, EPICS_PATCH_LEVEL) +#endif + /* include generated headers with: * EPICS_PVD_MAJOR_VERSION * EPICS_PVD_MINOR_VERSION diff --git a/testApp/misc/Makefile b/testApp/misc/Makefile index 7081ad3..aca89f1 100644 --- a/testApp/misc/Makefile +++ b/testApp/misc/Makefile @@ -72,3 +72,7 @@ TESTS += testTypeCast TESTPROD_HOST += testUnitTest testUnitTest_SRCS += testUnitTest.cpp TESTS += testUnitTest + +TESTPROD_HOST += testjson +testjson_SRCS += testjson.cpp +TESTS += testjson diff --git a/testApp/misc/testjson.cpp b/testApp/misc/testjson.cpp new file mode 100644 index 0000000..b6c5e37 --- /dev/null +++ b/testApp/misc/testjson.cpp @@ -0,0 +1,240 @@ +/* + * Copyright information and license terms for this software can be + * found in the file LICENSE that is included with the distribution + */ + +#include + +#include + +#if EPICS_VERSION_INT>=VERSION_INT(3,15,0,1) + +#include +#include +#include + +namespace pvd = epics::pvData; + +namespace { + +void testparseany() +{ + testDiag("testparseany()"); + std::istringstream strm("{\"hello\":42, \"one\":{\"two\":\"test\"}}"); + + pvd::PVStructurePtr val(pvd::parseJSON(strm)); + + std::cout<(val, "hello", 42); + testFieldEqual(val, "one.two", "test"); +} + +void testparseanyarray() +{ + testDiag("testparseanyarray()"); + std::istringstream strm("{\"hello\":[42, 43]}"); + + pvd::PVStructurePtr val(pvd::parseJSON(strm)); + + std::cout<(val, "hello", sarr); +} + +void testparseanyjunk() +{ + testDiag("testparseanyjunk()"); + + // things we can't store + { + testThrows(std::runtime_error, std::istringstream strm("4"); std::cout<createFieldBuilder() + ->add("scalar", pvd::pvInt) + ->addArray("ivec", pvd::pvLong) + ->addArray("svec", pvd::pvString) + ->addNestedStructure("sub") + ->addNestedStructure("x") + ->add("y", pvd::pvInt) + ->endNested() + ->endNested() + ->addNestedStructureArray("sarr") + ->add("a", pvd::pvInt) + ->add("b", pvd::pvInt) + ->endNested() + ->createStructure() + ); + +void testInto() +{ + testDiag("testInto()"); + + pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype)); + + std::istringstream strm(bigtest); + + std::cout<(val, "scalar", 42); + testFieldEqual(val, "sub.x.y", 43); + { + pvd::PVLongArray::svector expect(3); + expect[0] = 1; + expect[1] = 2; + expect[2] = 3; + testFieldEqual(val, "ivec", pvd::freeze(expect)); + } + { + pvd::PVStringArray::svector expect(2); + expect[0] = "one"; + expect[1] = "two"; + testFieldEqual(val, "svec", pvd::freeze(expect)); + } + { + pvd::PVStructureArrayPtr sarr(val->getSubFieldT("sarr")); + pvd::PVStructureArray::const_svector elems(sarr->view()); + testEqual(elems.size(), 3u); + if(elems.size()<3) + testAbort("Missing elements"); + testFieldEqual(elems[0], "a", 5); + testFieldEqual(elems[0], "b", 6); + testFieldEqual(elems[1], "a", 7); + testFieldEqual(elems[1], "b", 8); + testFieldEqual(elems[2], "a", 9); + testFieldEqual(elems[2], "b", 10); + } +} + +void testroundtrip() +{ + testDiag("testroundtrip()"); + + testDiag("Parse expected"); + pvd::PVStructurePtr val(pvd::getPVDataCreate()->createPVStructure(bigtype)); + { + std::istringstream strm(bigtest); + pvd::parseJSON(strm, val); + } + std::cout<createPVStructure(bigtype)); + { + std::istringstream strm(round1); + pvd::parseJSON(strm, val2); + } + + testEqual(*val, *val2); + + testDiag("print value"); + std::string round2; + { + pvd::JSONPrintOptions opts; + opts.ignoreUnprintable = true; + opts.multiLine = false; + + std::ostringstream strm; + pvd::printJSON(strm, val, opts); + + round2 = strm.str(); + } + + testEqual(round2, "{\"scalar\": 42," + "\"ivec\": [1,2,3]," + "\"svec\": [\"one\",\"two\"]," + "\"sub\": {" + "\"x\": {" + "\"y\": 43" + "}}," + "\"sarr\": [{\"a\": 5,\"b\": 6}," + "{\"a\": 7,\"b\": 8}," + "{\"a\": 9,\"b\": 10}]" + "}"); +} + +} // namespace + +MAIN(testjson) +{ + testPlan(22); + try { + testparseany(); + testparseanyarray(); + testparseanyjunk(); + testInto(); + testroundtrip(); + }catch(std::exception& e){ + testAbort("Unexpected exception: %s", e.what()); + } + return testDone(); +} + +#else // EPICS_VERSION_INT + +MAIN(testjson) +{ + testPlan(1); + try { + testSkip(1, "JSON parser requires Base >=3.15.0.1"); + }catch(std::exception& e){ + testAbort("Unexpected exception: %s", e.what()); + } + return testDone(); +} +#endif //EPICS_VERSION_INT