#include "cmPrefix.h"
#include "cmGlobal.h"
#include "cmFloatTypes.h"
#include "cmRpt.h"
#include "cmErr.h"
#include "cmCtx.h"
#include "cmJson.h"
#include "cmMem.h"
#include "cmMallocDebug.h"
#include "cmLex.h"
#include "cmLinkedHeap.h"
#include "cmFile.h"


enum
{
  kLCurlyLexTId = kUserLexTId+1,
  kRCurlyLexTId,
  kLHardLexTId,
  kRHardLexTId,
  kColonLexTId,
  kCommaLexTId,
  kTrueLexTId,
  kFalseLexTId,
  kNullLexTId
};


typedef struct
{
  unsigned    id;
  const char* text;
} cmJsToken_t;

// serialization buffer header
typedef struct
{
  unsigned id;       // always set to 'json'
  unsigned byteCnt;  // count of bytes following this field (total buf bytes = byteCnt + (2*sizeof(unsigned)))
  unsigned nodeCnt;  // count of nodes in this buffer
  unsigned version;  // buffer serialization version number
} cmJsSerialHdr_t;

// serialization helper record
typedef struct
{
  cmJsSerialHdr_t hdr;
  char*           basePtr;
  char*           nextPtr;
  char*           endPtr;  
} cmJsSerial_t;

// deserialization helper record
typedef struct 
{
  unsigned    nodeCnt;
  unsigned    nodeIdx;
  const char* nextPtr;
  const char* endPtr;

} cmJsDeserial_t;


typedef struct
{
  cmErr_t            err;            // 
  cmLexH             lexH;           // parsing lexer 
  cmLHeapH_t         heapH;          // linked heap stores all node memory 
  cmJsonNode_t*      rootPtr;        // root of internal node tree
  cmJsonNode_t*      basePtr;        // base of parsing stack
  cmJsonNode_t*      lastPtr;        // top of parsing stack
  cmJsRC_t           rc;             // last error code
  char*              serialBufPtr;   // serial buffer pointer
  unsigned           serialByteCnt;  // count of bytes in serialBuf[]
  bool               reportErrPosnFl;// report the file posn of syntax errors
  bool               modifiedFl;     // the tree has been modified since it was created.
} cmJs_t;

cmJsToken_t _cmJsTokenArray[] = 
{
  { kLCurlyLexTId, "{" },
  { kRCurlyLexTId, "}" },
  { kLHardLexTId,  "[" },
  { kRHardLexTId,  "]" },
  { kColonLexTId,  ":" },
  { kCommaLexTId,  "," },
  { kTrueLexTId,   "true"},
  { kFalseLexTId,  "false"},
  { kNullLexTId,   "null" },
  { kErrorLexTId,""}
  
};

cmJsToken_t _cmJsNodeTypeLabel[] = 
{
  { kObjectTId, "object" },
  { kPairTId,   "pair"   },
  { kArrayTId,  "array"  },
  { kStringTId, "string" },
  { kIntTId,    "int"    },
  { kRealTId,   "real"   },
  { kNullTId,   "null"   },
  { kTrueTId,   "true"   },
  { kFalseTId,  "false"  },
  { kInvalidTId,"invalid"}
};

cmJsonH_t cmJsonNullHandle = cmSTATIC_NULL_HANDLE;

cmJsRC_t _cmJsonRemoveNode( cmJs_t* p, cmJsonNode_t* np, bool freeFl, bool balancePairsFl );
void     _cmJsonFreeNode( cmJs_t* p, cmJsonNode_t* np);

const char* _cmJsonNodeTypeIdToLabel( unsigned nodeTypeId )
{
  unsigned i;
  for(i=0; _cmJsNodeTypeLabel[i].id != kInvalidTId; ++i)
    if( _cmJsNodeTypeLabel[i].id == nodeTypeId )
      break;
  return _cmJsNodeTypeLabel[i].text;
}

unsigned _cmJsonNodeTypeLabelToId( const char* typeLabel )
{
  unsigned i;
  for(i=0; _cmJsNodeTypeLabel[i].id != kInvalidTId; ++i)
    if( strcmp(_cmJsNodeTypeLabel[i].text,typeLabel) == 0 )
      break;

  return _cmJsNodeTypeLabel[i].id;
  
}

cmJs_t* _cmJsonHandleToPtr( cmJsonH_t h )
{
  cmJs_t* p = (cmJs_t*)h.h;
  assert(p != NULL);
  p->rc = kOkJsRC;
  return p;
}


cmJsRC_t _cmJsonError( cmJs_t* p, cmJsRC_t rc, const char* fmt, ... )
{
  va_list vl;
  va_start(vl,fmt);
  rc = cmErrVMsg(&p->err,rc,fmt,vl);
  va_end(vl);
  return rc;
}

cmJsRC_t _cmJsonSyntaxError( cmJs_t* p, const char* fmt, ... )
{
  int bn = 1024;
  char buf[bn+1];
  buf[0]=0;
  buf[bn]=0;

  va_list vl;
  va_start(vl,fmt);

  if( p->reportErrPosnFl )
    snprintf(buf,bn,"Syntax error on line:%i column:%i. ",cmLexCurrentLineNumber(p->lexH),cmLexCurrentColumnNumber(p->lexH));

  int n = strlen(buf);
  vsnprintf(buf+n,bn-n,fmt,vl);
  va_end(vl);

  return cmErrMsg(&p->err,kSyntaxErrJsRC,"%s",buf);
}


// Note that the stack is formed by a linked list 
// which is chained together using the nodes parentPtr.
// This works beacause the parentPtr for object,array,
// and pair nodes (the only nodes on the stack) is not
// needed as long as the node is on the stack. Once the
// node is popped the parentPtr is then set from the
// new stack top.
cmJsRC_t _cmJsonPushNode( cmJs_t* p, cmJsonNode_t* np )
{
  np->ownerPtr = p->lastPtr;
  p->lastPtr  = np;

  if( p->basePtr == NULL )
    p->basePtr = np;

  return kOkJsRC;
}

cmJsRC_t _cmJsonPopNode( cmJs_t* p )
{
  if( p->lastPtr == NULL )
    return _cmJsonSyntaxError(p,"A parser stack underlow signalled a syntax error.");

  cmJsonNode_t* t = p->lastPtr;

  // remove the top element
  p->lastPtr = p->lastPtr->ownerPtr;

  // set the parent of the popped node
  t->ownerPtr = p->lastPtr;

  if( p->lastPtr == NULL )
    p->basePtr = NULL;

  return kOkJsRC;
}



cmJsRC_t      cmJsonInitialize( cmJsonH_t* hp, cmCtx_t* ctx )
{
  cmJsRC_t rc;
  cmJs_t*  p;
  unsigned i;

  // finalize before initialize 
  if((rc = cmJsonFinalize(hp)) != kOkJsRC )
    return rc;

  // allocate the main object record
  if((p = cmMemAllocZ( cmJs_t, 1 )) == NULL )
    return cmErrMsg(&ctx->err,kMemAllocErrJsRC,"Object memory allocation failed.");

  cmErrSetup(&p->err,&ctx->rpt,"JSON Parser");

  // allocate the linked heap mgr
  if( cmLHeapIsValid(p->heapH = cmLHeapCreate(1024,ctx)) == false )
  {
    rc = _cmJsonError(p,kMemAllocErrJsRC,"Linked heap object allocation failed.");
    goto errLabel;
  }

  // allocate the lexer
  if(cmLexIsValid(p->lexH = cmLexInit(NULL,0,0,&ctx->rpt)) == false )
  {
    rc =  _cmJsonError(p,kLexErrJsRC,"Lex allocation failed.");
    goto errLabel;
  }

  // register json specific tokens with the lexer
  for(i=0; _cmJsTokenArray[i].id != kErrorLexTId; ++i)
  {
    cmRC_t lexRC;
    if( (lexRC = cmLexRegisterToken(p->lexH, _cmJsTokenArray[i].id, _cmJsTokenArray[i].text )) != kOkLexRC )
    {
      rc = _cmJsonError(p,kLexErrJsRC,"Lex token registration failed for:'%s'.\nLexer Error:%s",_cmJsTokenArray[i].text, cmLexRcToMsg(lexRC) );
      goto errLabel;
    }
  }

  hp->h = p;

  return kOkJsRC;
      
  errLabel:
  
  cmMemPtrFree(&p);

  if( cmLHeapIsValid(p->heapH) )
    cmLHeapDestroy(&p->heapH);

  if( cmLexIsValid(p->lexH) )
    cmLexFinal(&p->lexH);

  return rc;
}

cmJsRC_t      cmJsonInitializeFromFile( cmJsonH_t* hp, const char* fn, cmCtx_t* ctx )
{
  cmJsRC_t jsRC;

  if((jsRC = cmJsonInitialize(hp,ctx)) != kOkJsRC )
    return jsRC;

  if((jsRC = cmJsonParseFile(*hp,fn,NULL)) != kOkJsRC )
    cmJsonFinalize(hp);

  return jsRC;
}

cmJsRC_t      cmJsonInitializeFromBuf( cmJsonH_t* hp, cmCtx_t* ctx, const char* buf, unsigned bufByteCnt )
{
  cmJsRC_t jsRC;

  if((jsRC = cmJsonInitialize(hp,ctx)) != kOkJsRC )
    return jsRC;

  if((jsRC = cmJsonParse(*hp,buf,bufByteCnt,NULL)) != kOkJsRC )
    cmJsonFinalize(hp);

  return jsRC;
}

cmJsRC_t      cmJsonFinalize(   cmJsonH_t* hp )
{
  cmRC_t lexRC;

  if( hp == NULL || hp->h == NULL )
    return kOkJsRC;

  cmJs_t* p = _cmJsonHandleToPtr(*hp);
 
  // free the internal heap object
  cmLHeapDestroy( &p->heapH );

  // free the lexer
  if( cmLexIsValid(p->lexH) )
    if((lexRC = cmLexFinal(&p->lexH)) != kOkLexRC )
      return _cmJsonError(p,kLexErrJsRC,"Lexer finalization failed.\nLexer Error:%s",cmLexRcToMsg(lexRC));

  cmMemPtrFree(&p->serialBufPtr);

  // free the handle
  cmMemPtrFree(&hp->h);

  return kOkJsRC;
}



bool  cmJsonIsValid( cmJsonH_t h )
{ return h.h != NULL; }

bool  cmJsonIsModified( cmJsonH_t h )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return p->modifiedFl;
}

cmJsRC_t _cmJsonLinkInChild( cmJs_t* p, cmJsonNode_t* parentPtr, cmJsonNode_t* np )
{
  cmJsRC_t rc = kOkJsRC;

  np->ownerPtr = parentPtr;

  switch( parentPtr->typeId )
  {
    case kObjectTId:
    case kArrayTId:
    case kPairTId:
      {
        // if the parent is an 'object' then the child must be a 'pair'
        if( parentPtr->typeId == kObjectTId && np->typeId != kPairTId )
          rc = _cmJsonSyntaxError(p,"Expect only 'pair' nodes as children of 'objects'.");

        // if the parent is a 'pair' then is may have a max of two children
        if( parentPtr->typeId == kPairTId && cmJsonChildCount(parentPtr) >= 2 )
          rc = _cmJsonSyntaxError(p,"'pair' nodes may only have 2 children.");

          
        // insert the new node into the parent child list

        // if the new node is the first child
        if( parentPtr->u.childPtr == NULL )
          parentPtr->u.childPtr = np;
        else
        {
          // if the new node is the second or greater child
          cmJsonNode_t* lp = parentPtr->u.childPtr;
          while( lp->siblingPtr != NULL )
            lp = lp->siblingPtr;
          
          lp->siblingPtr = np;
        }
      }
      break;



    default:
      rc = _cmJsonSyntaxError(p,"'%s' nodes cannot be parent nodes.",_cmJsonNodeTypeIdToLabel(parentPtr->typeId));
      break;
  }

  return rc;
}

// This function creates nodes it also:
// 1. inserts array elments into their parents child list
// 2. inserts pairs into their parents member list
// 3. assigns values to pairs
cmJsRC_t _cmJsonCreateNode( cmJs_t* p, cmJsonNode_t* parentPtr, unsigned newNodeTypeId, cmJsonNode_t** npp )
{
  cmJsRC_t rc = kOkJsRC;

  cmJsonNode_t* np;

  assert( npp != NULL );

  *npp = NULL;

  // allocate the new node
  if((np = cmLHeapAllocZ( p->heapH, sizeof(cmJsonNode_t) )) == NULL )
    return _cmJsonError(p,kMemAllocErrJsRC,"Error allocating node memory.");

  // set the new node type
  np->typeId  = newNodeTypeId;
  np->ownerPtr = parentPtr;

  if( parentPtr == NULL )
  {
    if( newNodeTypeId != kObjectTId && newNodeTypeId != kArrayTId )
      rc = _cmJsonSyntaxError(p,"'%s' nodes must have a parent node.",_cmJsonNodeTypeIdToLabel(newNodeTypeId));
  }
  else
  {
    // if the parent is an 'object', 'array', or 'pair' then the
    // new node must be a child - insert it into the parent child list
    if((rc = _cmJsonLinkInChild( p, parentPtr, np )) != kOkJsRC )
      return rc;

    switch( newNodeTypeId )
    {
      case kObjectTId:
      case kArrayTId:
        break;

      case kPairTId:
        if( parentPtr == NULL || parentPtr->typeId != kObjectTId )
          rc = _cmJsonSyntaxError(p,"'pair' nodes must be the child of an object 'node'.");
        break;

      default:
        if( parentPtr == NULL )
          rc = _cmJsonSyntaxError(p,"'%s' nodes must have parent nodes.",_cmJsonNodeTypeIdToLabel(newNodeTypeId));
    }
 
  }

  // assign the return value
  *npp = np;

  if( p->rootPtr == NULL)
  {
    if( np->typeId != kObjectTId && np->typeId != kArrayTId )
      rc = _cmJsonSyntaxError(p,"The root object must be an 'object' or 'array'.");

    p->rootPtr = np;
  }

  p->modifiedFl = true;

  return rc;
}


cmJsRC_t _cmJsonCreateNumber( cmJs_t* p, cmJsonNode_t* parentPtr, unsigned nodeTId, cmJsonNode_t** npp )
{
  // numbers may only occurr as children of a 'pair' or element of an 'array'
  if( (parentPtr==NULL) || (parentPtr->typeId != kPairTId && parentPtr->typeId != kArrayTId) )
    return _cmJsonSyntaxError(p, "The parent of scalar:%*s is not a 'pair' or 'array'.", cmLexTokenCharCount(p->lexH), cmLexTokenText(p->lexH) ); 

  return _cmJsonCreateNode(p,parentPtr,nodeTId,npp);

}

cmJsRC_t _cmJsonCreateReal(cmJs_t* p, cmJsonNode_t* parentPtr,  double v, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t      rc;
  cmJsonNode_t* np = NULL;

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = NULL;

  if((rc= _cmJsonCreateNumber(p,parentPtr,kRealTId,&np)) == kOkJsRC )
  {
    np->u.realVal = v;

    if( newNodePtrPtr != NULL)
      *newNodePtrPtr = np;
  }
  return rc;  
}

cmJsRC_t _cmJsonCreateInt(cmJs_t* p, cmJsonNode_t* parentPtr,  int v, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t      rc;
  cmJsonNode_t* np = NULL;

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = NULL;

  if((rc= _cmJsonCreateNumber(p,parentPtr,kIntTId,&np)) == kOkJsRC )
  {
    np->u.intVal = v;

    if( newNodePtrPtr != NULL )
      *newNodePtrPtr = np;
  }

  return rc;  
}

cmJsRC_t _cmJsonCreateBool(cmJs_t* p, cmJsonNode_t* parentPtr,  bool fl, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t      rc;
  cmJsonNode_t* np = NULL;

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = NULL;
  

  if((rc= _cmJsonCreateNumber(p,parentPtr,fl?kTrueTId:kFalseTId,&np)) == kOkJsRC )
  {
    np->u.boolVal = fl;

    if( newNodePtrPtr != NULL )
      *newNodePtrPtr = np;
  }

  return rc;  
}

cmJsRC_t _cmJsonCreateNull(cmJs_t* p, cmJsonNode_t* parentPtr, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t rc;
  cmJsonNode_t* np = NULL;

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = NULL;
  
  if((rc = _cmJsonCreateNumber(p,parentPtr,kNullTId,&np)) == kOkJsRC )
  {
    if( newNodePtrPtr != NULL )
      *newNodePtrPtr = np;
  }
  return rc;
}



cmJsRC_t _cmJsonEscapeInt( cmJs_t* p, const char* sp, unsigned i, unsigned n, unsigned hexDigitCnt, char* rp )
{
  char buf[hexDigitCnt+1];

  memset(buf,0,hexDigitCnt+1);

  // fill buf[] with the next two characters
  unsigned j;
  for(j=0; i<n && j<hexDigitCnt; ++i,++j)
    buf[j] = sp[i];
    
  // be sure the buffer was filled - we must get exactly two characters
  if( i == n )
    return _cmJsonError(p,kInvalidHexEscapeJsRC,"An invalid hex escape code was encountered.");
 
  // do the text to int conversion
  errno = 0;
  long val = strtol(buf,NULL,16);

  // validate the conversion
  if( errno != 0 )
    return _cmJsonError(p,kInvalidHexEscapeJsRC,"Hex escape value conversion failed.");

  // convert the long to a character
  if( val > 0xff )
    return _cmJsonError(p,kInvalidHexEscapeJsRC,"Hex escape value is out of range (0x00-0xff).");
  
  *rp = (char)val;

  return kOkJsRC;
} 

cmJsRC_t _cmJsonEscapeString( cmJs_t* p, char* dp, const char* sp, unsigned n )
{
  cmJsRC_t rc;
  unsigned hexDigCnt = 2;  // count of digits in \u notation
  unsigned i,j;
  for(i=0,j=0; i<n; ++i,++j)
  {
    if( (sp[i] == '\\') && (i<(n-1) ))
    {
      switch( sp[i+1] )
      {
        case 'b': dp[j] = '\b'; break;
        case 'f': dp[j] = '\f'; break;
        case 'n': dp[j] = '\n'; break;
        case 'r': dp[j] = '\r'; break;
        case 't': dp[j] = '\t'; break;
        case 'u':
          {
            if((rc = _cmJsonEscapeInt( p, sp, i+2, n, hexDigCnt, dp+j )) != kOkJsRC )
              return rc;

            i += hexDigCnt; // skip hex digits

          }
          break;

        default:
          dp[j] = sp[i+1];
          break;
      }          
      ++i; // skip the escape character '\'
    }
    else
    {
      dp[j] = sp[i];
          
    }
  }

  if( j < n )
    dp[j] = 0;

  return kOkJsRC;
} 

// if 'cp' is NULL or 'cn' is 0 then the current string is dealloc'd 
// and the internal string value is left as NULL.
cmJsRC_t _cmJsonSetString( cmJs_t* p, cmJsonNode_t* np, const char* cp, unsigned cn )
{
  assert( np->typeId == kStringTId );

  cmJsRC_t      rc = kOkJsRC;

  // deallocate the current string
  if( np->u.stringVal != NULL )
  {
    cmLHeapFree(p->heapH,np->u.stringVal);
    np->u.stringVal = NULL;
  }

  if( cp != NULL && cn>0 )
  {

    // allocate the node data memory to hold the string
    if((np->u.stringVal = cmLHeapAllocZ(p->heapH,cn+1)) == NULL )
      return _cmJsonError(p,kMemAllocErrJsRC,"Unable to allocate stirng memory.");

    // copy the string into the node data memory
    if((rc = _cmJsonEscapeString(p, np->u.stringVal, cp, cn)) != kOkJsRC )
      return rc;


    np->u.stringVal[cn]=0;
  }
  return rc;
}

cmJsRC_t _cmJsonCreateString(cmJs_t* p, cmJsonNode_t* parentPtr,  const char* cp, unsigned cn, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t      rc;
  cmJsonNode_t* np = NULL;

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = NULL;
  
  // strings may only occurr as children of a 'pair' or element of an 'array'
  if( (parentPtr->typeId != kPairTId && parentPtr->typeId != kArrayTId ) )
    return _cmJsonSyntaxError(p, "The parent of string:%*s is not a 'pair', 'array', or 'object'.", cn,cp);

  // create the new node
  if((rc = _cmJsonCreateNode(p,parentPtr,kStringTId,&np)) != kOkJsRC )
    return rc;

  if((rc = _cmJsonSetString(p,np,cp,cn)) != kOkJsRC )
    return rc;

  /*
  // allocate the node data memory to hold the string
  if((np->u.stringVal = cmLHeapAllocZ(p->heapH,cn+1)) == NULL )
    return _cmJsonError(p,kMemAllocErrJsRC,"Unable to allocate stirng memory.");

  if((rc = _cmJsonEscapeString(p, np->u.stringVal, cp, cn)) != kOkJsRC )
    return rc;

  // copy the string into the node data memory
  //strncpy(np->u.stringVal,cp,cn);
  np->u.stringVal[cn]=0;
  */

  if( newNodePtrPtr != NULL )
    *newNodePtrPtr = np;


  return rc;  
}

cmJsRC_t _cmJsonCreatePair( cmJs_t* p, cmJsonNode_t* parentPtr, const char* label, cmJsonNode_t** newNodePtrPtr )
{
  cmJsRC_t rc;

  if((rc = _cmJsonCreateNode(p,parentPtr,kPairTId,newNodePtrPtr)) != kOkJsRC )
    return rc;

  return _cmJsonCreateString( p, *newNodePtrPtr, label, strlen(label), NULL);
}


// if fn == NULL then the buffer must contain the text to parse
cmJsRC_t _cmJsonParse(cmJsonH_t h, const char* buf, unsigned bufCharCnt, const cmChar_t* fn, cmJsonNode_t* altRootPtr )
{
  unsigned      lexTId = kErrorLexTId;
  cmJs_t*       p      = _cmJsonHandleToPtr(h);
  cmJsonNode_t* cnp    = altRootPtr == p->rootPtr ? altRootPtr : p->rootPtr;
  cmJsonNode_t* nnp    = NULL;
  cmJsRC_t      rc;

  p->reportErrPosnFl = true;

  // assign the text buffer and reset the lexer
  if( fn == NULL )
    rc = cmLexSetTextBuffer( p->lexH, buf, bufCharCnt );
  else
    rc = cmLexSetFile( p->lexH, fn );

  if( rc != kOkLexRC )
    return _cmJsonError( p, kLexErrJsRC, "Error setting lexer buffer.");
  
  // get the next token
  while( (lexTId = cmLexGetNextToken( p->lexH )) != kErrorLexTId && (lexTId != kEofLexTId ) && (rc==kOkJsRC) )
  {
    cnp = p->lastPtr;

    // if cnp is a pair and it's value has been assigned
    if( cnp != NULL && cnp->typeId == kPairTId && cmJsonChildCount(cnp)==2 )
    {
      if((rc = _cmJsonPopNode(p)) != kOkJsRC )
        break;

      cnp = p->lastPtr;
    }
    
    switch( lexTId )
    {
      case kRealLexTId:     // real number
        rc = _cmJsonCreateReal(p, cnp,  cmLexTokenDouble( p->lexH ), NULL);
        break;

      case kHexLexTId:      // hexidecimal integer
        // allow hex integers to be equivalent to decimal integers

      case kIntLexTId:      // decimal integer
        rc = _cmJsonCreateInt(p, cnp, cmLexTokenInt( p->lexH ), NULL );
        break;

      case kTrueLexTId:     // true
        rc = _cmJsonCreateBool(p, cnp, true, NULL );
        break;

      case kFalseLexTId:    // false
        rc = _cmJsonCreateBool(p, cnp, false, NULL );
        break;

      case kNullLexTId:     // null
        rc = _cmJsonCreateNull(p, cnp, NULL );
        break;

      case kIdentLexTId:    // identifier
        // allow identifiers to be equivalent to strings.

      case kQStrLexTId:     // quoted string
        if( cnp == NULL )
          rc = _cmJsonSyntaxError(p,"Encountered a 'string' with no parent.");
        else
          if( cnp->typeId == kObjectTId )
          {
            if((rc = _cmJsonCreateNode(p,cnp,kPairTId,&nnp)) == kOkJsRC )
            {
              _cmJsonPushNode(p,nnp);
              cnp = nnp;
            }

            //if((rc = _cmJsonCreateNewParent(p,cnp,kPairTId)) == kOkJsRC )
            //  cnp = p->lastPtr;
          }

        if( rc == kOkJsRC )
          rc = _cmJsonCreateString(p, cnp, cmLexTokenText(p->lexH), cmLexTokenCharCount(p->lexH), NULL);
        break;

      case kColonLexTId:
        if( cnp->typeId != kPairTId )
          rc = _cmJsonSyntaxError(p,"A colon was found outside of a 'pair' element.");
        break;

      case kLCurlyLexTId:   // {
        //rc = _cmJsonCreateNewParent(p, cnp, kObjectTId );
        if((rc = _cmJsonCreateNode(p,cnp,kObjectTId,&nnp)) == kOkJsRC )
          _cmJsonPushNode(p,nnp);
        break;

      case kRCurlyLexTId:   // }
        if( cnp == NULL || cnp->typeId != kObjectTId )
          rc = _cmJsonSyntaxError(p,"A '}' was found without an accompanying opening bracket.");
        else
          rc = _cmJsonPopNode(p);
        break;

      case kLHardLexTId:    // [
        //rc = _cmJsonCreateNewParent(p, cnp, kArrayTId);
        if((rc = _cmJsonCreateNode(p,cnp,kArrayTId,&nnp)) == kOkJsRC )
          _cmJsonPushNode(p,nnp);
        break;

      case kRHardLexTId:    // ]
        if( cnp == NULL || cnp->typeId != kArrayTId )
          rc = _cmJsonSyntaxError(p,"A ']' was found without an accompanying opening bracket.");
        else
          rc = _cmJsonPopNode(p);
        break;

      case kCommaLexTId:    // ,
        if( (cnp==NULL) || (cnp->typeId != kArrayTId && cnp->typeId != kObjectTId) )
          rc = _cmJsonSyntaxError(p,"Commas may only occur in 'array' and 'object' nodes.");

        break;


      case kSpaceLexTId:    // white space
      case kBlockCmtLexTId: //  block comment
      case kLineCmtLexTId:  // line comment 
        assert(0); 
        break;

      default:
        break;
    }
  }

  if( lexTId == kErrorLexTId )
    rc = _cmJsonSyntaxError( p, "The lexer failed: %s.", cmLexRcToMsg(cmLexErrorRC(p->lexH)));

  p->reportErrPosnFl = false;
  p->modifiedFl      = false;

  return rc;
}

cmJsRC_t      cmJsonParse(      cmJsonH_t h, const char* buf, unsigned bufCharCnt, cmJsonNode_t* altRootPtr )
{ return _cmJsonParse(h,buf,bufCharCnt,NULL,altRootPtr); }

cmJsRC_t      cmJsonParseFile(  cmJsonH_t h, const char* fn, cmJsonNode_t* altRootPtr )
{ return _cmJsonParse(h,NULL,0,fn,altRootPtr); }

cmJsonNode_t* cmJsonRoot(  cmJsonH_t h )
{  
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return p->rootPtr;
}


cmJsRC_t      cmJsonClearTree(      cmJsonH_t h )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  p->rootPtr = NULL;
  p->basePtr = NULL;
  p->lastPtr = NULL;
  
  cmLHeapClear(p->heapH,true);

  return kOkJsRC;
}

bool cmJsonIsObject( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kObjectTId); }
bool cmJsonIsArray(  const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kArrayTId); }
bool cmJsonIsPair(   const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kPairTId); }
bool cmJsonIsString( const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kStringTId); }
bool cmJsonIsInt(    const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kIntTId); }
bool cmJsonIsReal(   const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kRealTId); }
bool cmJsonIsBool(   const cmJsonNode_t* np ) { return cmIsFlag(np->typeId,kTrueTId | kFalseTId); }



unsigned cmJsonChildCount( const cmJsonNode_t* np )
{
  if( np == NULL )
    return 0;

  unsigned n = 0;
  switch( np->typeId )
  {
    case kObjectTId:
    case kArrayTId:
    case kPairTId:
      {        
        const cmJsonNode_t* lp = np->u.childPtr;

        while( lp != NULL )
        {
          ++n;
          lp = lp->siblingPtr;
        }

      }
      break;

    default:
      break;
  }
  return n;
}

cmJsonNode_t* cmJsonArrayElement( cmJsonNode_t* np, unsigned index )
{
  unsigned i;

  assert( index < cmJsonChildCount(np));

  np = np->u.childPtr;

  for(i=0; i<index; ++i)
    np = np->siblingPtr;

  return np;
}

const cmJsonNode_t* cmJsonArrayElementC( const cmJsonNode_t* np, unsigned index )
{ return cmJsonArrayElement( (cmJsonNode_t*)np, index ); }

const char* cmJsonPairLabel(  const cmJsonNode_t* pairPtr )
{
  assert( pairPtr->typeId == kPairTId );

  if( pairPtr->typeId != kPairTId )
    return NULL;

  return pairPtr->u.childPtr->u.stringVal;
}

unsigned cmJsonPairTypeId( const cmJsonNode_t* pairPtr )
{
  assert( pairPtr->typeId == kPairTId );
  return pairPtr->u.childPtr->siblingPtr->typeId; 
}

cmJsonNode_t* cmJsonPairValue( cmJsonNode_t* pairPtr )
{
  assert( pairPtr->typeId == kPairTId );

  if( pairPtr->typeId != kPairTId )
    return NULL;

  return pairPtr->u.childPtr->siblingPtr;

}

cmJsonNode_t* cmJsonFindValue( cmJsonH_t h, const char* label, const cmJsonNode_t* np, unsigned keyTypeMask )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  
  if( np == NULL )
    np = p->rootPtr;

  if( np == NULL )
    return NULL;

  // we are only interested in pairs
  if( np->typeId == kPairTId )
  {
    // pairs must have exactly two nodes - the first must be a string 
    assert(np->u.childPtr != NULL && np->u.childPtr->typeId == kStringTId && np->u.childPtr->siblingPtr != NULL );

    if( strcmp(cmJsonPairLabel(np),label) == 0 )
    {
      if( (keyTypeMask==kInvalidTId) || (keyTypeMask & np->u.childPtr->siblingPtr->typeId) )
        return np->u.childPtr->siblingPtr;
    }
  }

  // if the node is an object,array, or pair ...
  if( np->typeId==kObjectTId || np->typeId==kArrayTId || np->typeId==kPairTId )
  {
    // ... then recurse on its children
    cmJsonNode_t* cnp = np->u.childPtr;
    while(cnp != NULL)
    {
      cmJsonNode_t* rp;

      if((rp = cmJsonFindValue(h,label,cnp,keyTypeMask)) != NULL )
        return rp;

      cnp = cnp->siblingPtr;
    }
  }

  return NULL;
}


cmJsRC_t  _cmJsonFindPathValue( cmJs_t* p, const char* pathPrefix, const char* path, const cmJsonNode_t* rp, const cmJsonNode_t** rpp )
{
  cmJsRC_t rc = kOkJsRC;

  if( rp == NULL )
    rp = p->rootPtr;

  if( rp == NULL )
    return kOkJsRC;  

  assert( cmJsonIsObject(rp));
  assert( rpp != NULL );
  
  *rpp = NULL;

  // create a copy of the path
  unsigned i,j;
  unsigned sn = (pathPrefix==NULL ? 0 : strlen(pathPrefix))  + strlen(path) + 1; // add one for the possible extra seperator
  char ss[ 1024 ];
  char* sp = NULL;
  char* sm = NULL;
  char* sb = ss;
  
  // don't put more than 1k on the stack
  if( sn + 1 > 1024 )
  {
    sm = cmMemAllocZ(char,sn+1);
    sb = sm;
  }

  sp  = sb;

  sp[0] = 0;
  if(pathPrefix != NULL )
  {
    strcpy(sp,pathPrefix);

    // if pathPrefix does not end in  a '/' then insert one
    if( sp[ strlen(sp)-1 ] != '/' )
      strcat(sp,"/");
  }

  // the '/' has already been inserted - skip any leading '/' character in path
  strcat(sp,path[0]=='/' ? path+1 : path );

  // terminate each path label with a '/0'.
  sp = ss;
  for(i=0,j=0; sp < ss + sn; ++sp, ++i )
    if( *sp == '/' )
    {
      *sp = 0;
      ++j;
    }

  if( i > 0 )
  {

    sp = sb;

    while( sp < sb + sn )
    {
      
      // labeled values are always associated with pairs and
      // pairs only exist as the children of objects.
      if( cmJsonIsObject(rp) == false )
      {
        rc = cmErrMsg(&p->err,kInvalidNodeTypeJsRC,"A non-object node was encountered on a object path.");
        break;
      }

      // get the count of pairs in this object
      unsigned      cn = cmJsonChildCount( rp );
      cmJsonNode_t* cp = rp->u.childPtr;  
      unsigned       k;

      for(k=0; k<cn; ++k)
      {
        // all children of an object must be pairs
        assert( cp != NULL && cmJsonIsPair(cp) );

        // if this is the labeled pair we are looking for
        if( strcmp(cmJsonPairLabel(cp),sp) == 0 )
          break;        

        cp = cp->siblingPtr;
      }

      // if the search failed
      if( k == cn || cp == NULL )
      {
        rc = cmErrMsg(&p->err,kNodeNotFoundJsRC,"The path label '%s' could not be found.",cmStringNullGuard(sp));
        break;
      }

      // take the value of the located pair to continue the search
      rp = cmJsonPairValue(cp);

      // advance to the next label
      sp += strlen(sp) + 1;

    }

    
  }

  cmMemPtrFree(&sm);

  *rpp = rp;

  return rc;
}

cmJsRC_t _cmJsonPathToValueError( cmJs_t* p, cmJsRC_t rc, const char* pathPrefix, const char* path, const char* typeLabel )
{
  if( pathPrefix != NULL )
    cmErrMsg(&p->err,rc,"The JSON value at '%s/%s' could not be converted to a '%s'.",cmStringNullGuard(pathPrefix),cmStringNullGuard(path),cmStringNullGuard(typeLabel));
  else
    cmErrMsg(&p->err,rc,"The JSON value at '%s' could not be converted to a '%s'.",cmStringNullGuard(path),cmStringNullGuard(typeLabel));

  return rc;
}

cmJsRC_t cmJsonPathToValueNode( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, const cmJsonNode_t** nodePtrPtr )
{
  cmJsRC_t rc = kOkJsRC;
  cmJs_t*  p  = _cmJsonHandleToPtr(h);

  if( objectNodePtr == NULL )
    objectNodePtr = p->rootPtr;

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,nodePtrPtr)) != kOkJsRC )
    return rc;
  
  return rc;
}

cmJsRC_t cmJsonPathToBool(   cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, bool* retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonBoolValue(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"bool");

  return rc;
}

cmJsRC_t cmJsonPathToInt(    cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, int* retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonIntValue(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"bool");

  return rc;
}

cmJsRC_t cmJsonPathToUInt(   cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, unsigned* retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonUIntValue(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"unsigned integer");

  return rc;
}

cmJsRC_t cmJsonPathToReal(   cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, double* retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonRealValue(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"real");

  return rc;
}

cmJsRC_t cmJsonPathToString( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, const char** retValPtr )
{
  const cmJsonNode_t* rp = NULL;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonStringValue(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"string");

  return rc;
}

cmJsRC_t cmJsonPathToPair(  cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonPairNode(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"pair");

  return rc;
}

cmJsRC_t cmJsonPathToArray(  cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonArrayNode(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"array");

  return rc;
}

cmJsRC_t cmJsonPathToObject( cmJsonH_t h, const cmJsonNode_t* objectNodePtr, const char* pathPrefix, const char* path, cmJsonNode_t** retValPtr )
{
  const cmJsonNode_t* rp;
  cmJsRC_t            rc = kOkJsRC;
  cmJs_t*             p  = _cmJsonHandleToPtr(h);

  if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objectNodePtr,&rp)) != kOkJsRC )
    return rc;

  if((rc = cmJsonObjectNode(rp,retValPtr)) != kOkJsRC )
    return _cmJsonPathToValueError(p,rc,pathPrefix,path,"object");

  return rc;
}


const cmJsonNode_t*  cmJsonFindPathValueC( cmJsonH_t h, const char* path, const cmJsonNode_t* np, unsigned typeIdMask )
{
  cmJs_t*             p  = _cmJsonHandleToPtr(h);
  const cmJsonNode_t* rp = NULL;
  cmJsRC_t            rc = kOkJsRC;
  
  if((rc = _cmJsonFindPathValue(p,NULL,path,np,&rp)) != kOkJsRC )
  {
    // validate the return type
    if( rp != NULL )
      if( (typeIdMask!=kInvalidTId) && (typeIdMask & rp->typeId)==0 )
      {
        cmErrMsg(&p->err,kInvalidNodeTypeJsRC,"The value at the end of the path '%s' did not match the requested type.",cmStringNullGuard(path));
        rp = NULL;
      }
  }

  return rp;
}

cmJsonNode_t*  cmJsonFindPathValue( cmJsonH_t h, const char* path, const cmJsonNode_t* np, unsigned typeIdMask )
{ return (cmJsonNode_t*)cmJsonFindPathValueC(h,path,np,typeIdMask ); }

cmJsRC_t  _cmJsonFindMemberValue( const cmJsonNode_t* np, const char* label, unsigned keyTypeId, cmJsonNode_t** npp )
{
  *npp = NULL;

  // the src node must be an object
  if( np->typeId != kObjectTId )
    return kNodeNotFoundJsRC;

  // for each member pair
  const cmJsonNode_t* cnp = np->u.childPtr;
  while( cnp != NULL )
  {
    assert( (cnp->typeId & kMaskTId) == kPairTId );

    // if the labels match ...
    if( strcmp( label, cmJsonPairLabel(cnp)) == 0 )
    {
      // ... and the type flags match ...
      if( (keyTypeId==kInvalidTId || cmIsFlag(cnp->u.childPtr->siblingPtr->typeId,keyTypeId) ) )
      {
        *npp = cnp->u.childPtr->siblingPtr;
        return kOkJsRC; // ... then the key was found.
      }

      // ... label match but wrong type ... this is considered an error
      return kNodeCannotCvtJsRC;
    }
  
    cnp = cnp->siblingPtr; 
  }

  return kNodeNotFoundJsRC;
}

cmJsRC_t   cmJsonUIntValue( const cmJsonNode_t* vp, unsigned* retPtr )
{
  cmJsRC_t rc = kOkJsRC;

  if( vp == NULL )
    return kNodeCannotCvtJsRC;

  switch(vp->typeId)
  {
    case kIntTId:    *retPtr = vp->u.intVal;       break;
    case kRealTId:   *retPtr = (unsigned)vp->u.realVal; break;
    case kTrueTId:   *retPtr = 1;                  break;
    case kFalseTId:  *retPtr = 0;                  break;
    default:
      rc = kNodeCannotCvtJsRC;
  }
  return rc;
}

cmJsRC_t   cmJsonIntValue( const cmJsonNode_t* vp, int* retPtr )
{
  cmJsRC_t rc = kOkJsRC;

  if( vp == NULL )
    return kNodeCannotCvtJsRC;

  switch(vp->typeId)
  {
    case kIntTId:    *retPtr = vp->u.intVal;       break;
    case kRealTId:   *retPtr = (int)vp->u.realVal; break;
    case kTrueTId:   *retPtr = 1;                  break;
    case kFalseTId:  *retPtr = 0;                  break;
    default:
      rc = kNodeCannotCvtJsRC;
  }
  return rc;
}

cmJsRC_t   cmJsonRealValue(   const cmJsonNode_t* vp, double*      retPtr )
{
  cmJsRC_t rc = kOkJsRC;

  if( vp == NULL )
    return kNodeCannotCvtJsRC;

  switch(vp->typeId)
  {
    case kIntTId:    *retPtr = vp->u.intVal;  break;
    case kRealTId:   *retPtr = vp->u.realVal; break;
    case kTrueTId:   *retPtr = 1;             break;
    case kFalseTId:  *retPtr = 0;             break;
    default:
      rc = kNodeCannotCvtJsRC;

  }
  return rc;

}

cmJsRC_t   cmJsonBoolValue(   const cmJsonNode_t* vp, bool*        retPtr )
{
  cmJsRC_t rc = kOkJsRC;

  if( vp == NULL )
    return kNodeCannotCvtJsRC;

  switch(vp->typeId)
  {
    case kIntTId:    *retPtr = vp->u.intVal != 0;       break;
    case kRealTId:   *retPtr = (int)vp->u.realVal != 0; break;
    case kTrueTId:   *retPtr = 1;                       break;
    case kFalseTId:  *retPtr = 0;                       break;
    default:
      rc = kNodeCannotCvtJsRC;

  }
  return rc;

}

cmJsRC_t   cmJsonStringValue( const cmJsonNode_t* vp, const char **retPtrPtr )  
{
  cmJsRC_t rc = kOkJsRC;
  
  if( vp == NULL || vp->typeId != kStringTId )
    return kNodeCannotCvtJsRC;

  *retPtrPtr = vp->u.stringVal;

  return rc;
}

cmJsRC_t   cmJsonPairNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr )  
{
  cmJsRC_t rc = kOkJsRC;

  if( !cmJsonIsPair(vp) )
    return kNodeCannotCvtJsRC;

  *retPtrPtr = (cmJsonNode_t*)vp;

  return rc;
}

cmJsRC_t   cmJsonArrayNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr )  
{
  cmJsRC_t rc = kOkJsRC;

  if( !cmJsonIsArray(vp) )
    return kNodeCannotCvtJsRC;

  *retPtrPtr = (cmJsonNode_t*)vp;

  return rc;
}

cmJsRC_t   cmJsonObjectNode( const cmJsonNode_t* vp, cmJsonNode_t **retPtrPtr )  
{
  cmJsRC_t rc = kOkJsRC;

  if( !cmJsonIsObject(vp) )
    return kNodeCannotCvtJsRC;

  *retPtrPtr = (cmJsonNode_t*)vp;

  return rc;
}

cmJsRC_t      cmJsonUIntMember(    const cmJsonNode_t* np, const char* label, unsigned* retPtr )
{ 
  cmJsonNode_t* vp;
  cmJsRC_t            rc;

  if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC )
    return rc;

  return cmJsonUIntValue(vp,retPtr);
}
 
cmJsRC_t      cmJsonIntMember(    const cmJsonNode_t* np, const char* label, int* retPtr )
{ 
  cmJsonNode_t* vp;
  cmJsRC_t            rc;

  if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC )
    return rc;

  return cmJsonIntValue(vp,retPtr);
}

cmJsRC_t      cmJsonRealMember(   const cmJsonNode_t* np, const char* label, double* retPtr )
{ 
  cmJsonNode_t* vp;
  cmJsRC_t            rc;

  if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC )
    return rc;

  return cmJsonRealValue(vp,retPtr);
}

cmJsRC_t      cmJsonBoolMember(   const cmJsonNode_t* np, const char* label, bool* retPtr )
{ 
  cmJsonNode_t* vp;
  cmJsRC_t            rc;

  if((rc = _cmJsonFindMemberValue(np,label,kNumericTId,&vp)) != kOkJsRC )
    return rc;

  return cmJsonBoolValue(vp,retPtr);
  
}

cmJsRC_t      cmJsonStringMember( const cmJsonNode_t* np, const char* label, const char** retPtrPtr )
{ 
  cmJsonNode_t* vp;
  cmJsRC_t            rc;

  *retPtrPtr = NULL;

  if((rc = _cmJsonFindMemberValue(np,label,kStringTId,&vp)) != kOkJsRC )
    return rc;

  return cmJsonStringValue(vp,retPtrPtr);
}

cmJsRC_t      cmJsonNodeMember(   const cmJsonNode_t* np, const char* label, cmJsonNode_t** retPtrPtr )
{
  cmJsonNode_t* vp;
  cmJsRC_t      rc;

  *retPtrPtr = NULL;

  if((rc = _cmJsonFindMemberValue(np,label,kArrayTId|kObjectTId,&vp)) != kOkJsRC )
    return rc;

  *retPtrPtr = vp;

  return rc;
}

cmJsonNode_t* cmJsonNodeMemberValue( const cmJsonNode_t* np, const char* label )
{
  assert( cmJsonIsObject(np) );

  unsigned n = cmJsonChildCount(np);
  unsigned j = 0;
  for(; j<n; ++j)
  {
    const cmJsonNode_t* cnp = cmJsonArrayElementC(np,j);

    assert( cnp != NULL && cmJsonIsPair(cnp) );

    if( strcmp(label,cmJsonPairLabel(cnp))==0 )
      return cmJsonPairValue((cmJsonNode_t*)cnp);
  }
  
  return NULL;
}

cmJsRC_t      cmJsonVMemberValues( const cmJsonNode_t* objectNodePtr, const char** errLabelPtrPtr, va_list vl )
{
  unsigned typeId;
  cmJsRC_t rc = kOkJsRC;

  if( errLabelPtrPtr != NULL )
    *errLabelPtrPtr = NULL;

  const char* label;
  while( ((label = va_arg(vl,const char*)) != NULL) && (rc==kOkJsRC) )
  {
    typeId = va_arg(vl,unsigned);

    switch( typeId & kMaskTId )
    {

      case kObjectTId:
      case kPairTId:
      case kArrayTId:
        {
          cmJsonNode_t** nodePtrPtr = va_arg(vl, cmJsonNode_t**);

          if((rc = cmJsonNodeMember(objectNodePtr,label,nodePtrPtr )) == kOkJsRC )
          {
            cmJsonNode_t* np = *nodePtrPtr;
            if( (np->typeId & kMaskTId) != (typeId&kMaskTId) )
              rc = kNodeCannotCvtJsRC;
          }
          
        }
        break;

      case kIntTId:        
        {
          int* ip = va_arg(vl, int* );
          assert(ip != NULL);
          rc = cmJsonIntMember(objectNodePtr, label, ip);
        }
        break;

      case kRealTId:
        {
          double* dp = va_arg(vl, double*);
          assert(dp != NULL);
          rc = cmJsonRealMember(objectNodePtr, label, dp);
        }
        break;

      case kStringTId:
        {
          const char** cpp = va_arg(vl, const char**);
          assert(cpp != NULL);
          rc = cmJsonStringMember(objectNodePtr, label, cpp);
        }
        break;
        
      case kTrueTId:
      case kFalseTId:
      case kBoolTId:
        {
          bool* bp = va_arg(vl, bool* );
          rc = cmJsonBoolMember(objectNodePtr, label, bp);
        }
        break;

      default:
        // missing terminating NULL on the var args list???
        assert(0);
        break;
    }

    if( (rc == kNodeNotFoundJsRC) && cmIsFlag(typeId,kOptArgJsFl) )
      rc = kOkJsRC;

    if( rc != kOkJsRC && errLabelPtrPtr != NULL )
      *errLabelPtrPtr = label;
      
  }

  return rc;
}

cmJsRC_t      cmJsonMemberValues( const cmJsonNode_t* objectNodePtr, const char** errLabelPtrPtr, ...)
{
  va_list vl;
  va_start(vl,errLabelPtrPtr);
  cmJsRC_t rc = cmJsonVMemberValues(objectNodePtr,errLabelPtrPtr,vl);
  va_end(vl);
  return rc;
}

cmJsRC_t      cmJsonVPathValues( cmJsonH_t h, const char* pathPrefix, const cmJsonNode_t* objNodePtr, const char** errLabelPtrPtr, va_list vl )
{
  cmJsRC_t rc = kOkJsRC;
  cmJs_t*  p  = _cmJsonHandleToPtr(h);

  if( errLabelPtrPtr != NULL )
    *errLabelPtrPtr = NULL;

  const char* path;
  while( ((path = va_arg(vl,const char*)) != NULL) && (rc==kOkJsRC) )
  {
    unsigned            typeId;
    const cmJsonNode_t* vnp;

    typeId = va_arg(vl,unsigned);

    // find the requested pair value
    if((rc = _cmJsonFindPathValue(p,pathPrefix,path,objNodePtr,&vnp)) != kOkJsRC )
      break;

    switch( typeId & kMaskTId )
    {
      case kObjectTId:
      case kPairTId:
      case kArrayTId:
        {
          const cmJsonNode_t** nodePtrPtr = va_arg(vl, const cmJsonNode_t**);

          if( (vnp->typeId & kMaskTId) != (typeId & kMaskTId) )
            rc = kNodeCannotCvtJsRC;
          else
            *nodePtrPtr = vnp;
          
        }
        break;

      case kIntTId:        
        {
          int* ip = va_arg(vl, int* );
          assert(ip != NULL);
          rc = cmJsonIntValue(vnp, ip);
        }
        break;

      case kRealTId:
        {
          double* dp = va_arg(vl, double*);
          assert(dp != NULL);
          rc = cmJsonRealValue(vnp, dp);
        }
        break;

      case kStringTId:
        {
          const char** cpp = va_arg(vl, const char**);
          assert(cpp != NULL);
          rc = cmJsonStringValue(vnp, cpp);
        }
        break;
        
      case kTrueTId:
      case kFalseTId:
        {
          bool* bp = va_arg(vl, bool* );
          rc = cmJsonBoolValue(vnp, bp);
        }
        break;

      default:
        // missing terminating NULL on the var args list???
        assert(0);
        break;

    }

    if( (rc == kNodeNotFoundJsRC) && cmIsFlag(typeId,kOptArgJsFl) )
      rc = kOkJsRC;

    if( rc != kOkJsRC && errLabelPtrPtr != NULL )
      *errLabelPtrPtr = path;
      
  }

  return rc;
}

cmJsRC_t      cmJsonPathValues( cmJsonH_t h, const char* pathPrefix, const cmJsonNode_t* objectNodePtr, const char** errLabelPtrPtr, ... )
{
  va_list vl;
  va_start(vl,errLabelPtrPtr);
  cmJsRC_t rc = cmJsonVPathValues(h,pathPrefix,objectNodePtr,errLabelPtrPtr,vl);
  va_end(vl);
  return rc;
}


cmJsonNode_t* _cmJsonCreateNode2( cmJsonH_t h, unsigned newNodeTypeId, cmJsonNode_t* parentPtr )
{
  cmJs_t*       p   = _cmJsonHandleToPtr(h);
  cmJsonNode_t* np  = NULL;

  if((p->rc = _cmJsonCreateNode(p,parentPtr,newNodeTypeId,&np)) != kOkJsRC )
    return NULL;

  return np;
}


cmJsRC_t cmJsonCreate( cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned typeId, const char* sv, int iv, double dv, cmJsonNode_t** rpp )
{
  cmJsonNode_t* rp = NULL;
  cmJsRC_t      rc = kOkJsRC;
  cmJs_t*       p  = _cmJsonHandleToPtr(h);

  if( rpp != NULL )
    *rpp = NULL;

  switch( typeId )
  {    
    case kObjectTId:
    case kArrayTId:  
      if((rp = _cmJsonCreateNode2(h,typeId,parentPtr)) == NULL)
        rc = p->rc;
      else
      {
        if( rpp != NULL )
          *rpp = rp;
      }
      break;
      
    case kPairTId:   rc = _cmJsonCreatePair(p,parentPtr,sv,rpp); break;
    case kIntTId:    rc = _cmJsonCreateInt(p,parentPtr,iv,rpp); break;
    case kRealTId:   rc = _cmJsonCreateReal(p,parentPtr,dv,rpp); break;
    case kTrueTId:   rc = _cmJsonCreateBool(p,parentPtr,true,rpp); break;
    case kFalseTId:  rc = _cmJsonCreateBool(p,parentPtr,false,rpp); break;
    case kNullTId:   rc = _cmJsonCreateNull(p,parentPtr,rpp); break;
    case kStringTId: rc = _cmJsonCreateString(p,parentPtr,sv,strlen(sv),rpp); break;
    default:
      assert(0);
      break;
  }

  return rc;
}


cmJsonNode_t* cmJsonCreateObject( cmJsonH_t h, cmJsonNode_t* parentPtr )
{ return _cmJsonCreateNode2(h,kObjectTId,parentPtr); }


cmJsonNode_t* cmJsonCreateArray(  cmJsonH_t h, cmJsonNode_t* parentPtr )
{ return _cmJsonCreateNode2(h,kArrayTId,parentPtr); }


cmJsonNode_t* cmJsonCreatePair(   cmJsonH_t h, cmJsonNode_t* parentPtr, const char* label )
{
  cmJsonNode_t* np;

  cmJs_t* p = _cmJsonHandleToPtr(h);

  if((_cmJsonCreatePair(p,parentPtr,label,&np)) != kOkJsRC )
    return NULL;

  return np;
}

cmJsRC_t      cmJsonCreateString( cmJsonH_t h, cmJsonNode_t* parentPtr, const char* stringValue )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateString( p, parentPtr, stringValue, strlen(stringValue),NULL);;
}

cmJsRC_t      cmJsonCreateStringN(cmJsonH_t h, cmJsonNode_t* parentPtr, const char* stringValue, unsigned stringCharCnt )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateString( p, parentPtr, stringValue, stringCharCnt,NULL);;
}

cmJsRC_t      cmJsonCreateInt(    cmJsonH_t h, cmJsonNode_t* parentPtr, int value )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateInt( p, parentPtr, value, NULL );
}

cmJsRC_t      cmJsonCreateReal(   cmJsonH_t h, cmJsonNode_t* parentPtr, double value )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateReal( p, parentPtr, value, NULL );
}

cmJsRC_t      cmJsonCreateBool(   cmJsonH_t h, cmJsonNode_t* parentPtr, bool value )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateBool( p, parentPtr, value,NULL );
}

 cmJsRC_t      cmJsonCreateNull(   cmJsonH_t h, cmJsonNode_t* parentPtr )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonCreateNull( p, parentPtr, NULL );
}

cmJsRC_t      cmJsonCreateStringArray( cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned n, const char** value )
{
  cmJs_t*       p  = _cmJsonHandleToPtr(h);
  cmJsRC_t      rc = kOkJsRC;
  cmJsonNode_t* np;
  unsigned      i;

  if((np = cmJsonCreateArray(h,parentPtr)) == NULL )
    return _cmJsonError(p,cmErrLastRC(&p->err),"Unable to create 'bool' array.");
  
  for(i=0; i<n; ++i)
    if((rc = cmJsonCreateString(h,np,value[i])) != kOkJsRC )
      return _cmJsonError(p,rc,"Unable to create 'bool' array element at index %i.",i);

  return rc;
}

cmJsRC_t      cmJsonCreateIntArray(    cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned n, const int* value )
{
  cmJs_t*       p  = _cmJsonHandleToPtr(h);
  cmJsRC_t      rc = kOkJsRC;
  cmJsonNode_t* np;
  unsigned      i;

  if((np = cmJsonCreateArray(h,parentPtr)) == NULL )
    return _cmJsonError(p,cmErrLastRC(&p->err),"Unable to create 'int' array.");
  
  for(i=0; i<n; ++i)
    if((rc = cmJsonCreateInt(h,np,value[i])) != kOkJsRC )
      return _cmJsonError(p,rc,"Unable to create 'int' array element at index %i.",i);

  return rc;

}

cmJsRC_t      cmJsonCreateRealArray(   cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned n, const double* value )
{
  cmJs_t*       p  = _cmJsonHandleToPtr(h);
  cmJsRC_t      rc = kOkJsRC;
  cmJsonNode_t* np;
  unsigned      i;

  if((np = cmJsonCreateArray(h,parentPtr)) == NULL )
    return _cmJsonError(p,cmErrLastRC(&p->err),"Unable to create 'real' array.");
  
  for(i=0; i<n; ++i)
    if((rc = cmJsonCreateReal(h,np,value[i])) != kOkJsRC )
      return _cmJsonError(p,rc,"Unable to create 'real' array element at index %i.",i);

  return rc;
}

cmJsRC_t      cmJsonCreateBoolArray(   cmJsonH_t h, cmJsonNode_t* parentPtr, unsigned n, const bool* value )
{
  cmJs_t*       p  = _cmJsonHandleToPtr(h);
  cmJsRC_t      rc = kOkJsRC;
  cmJsonNode_t* np;
  unsigned      i;

  if((np = cmJsonCreateArray(h,parentPtr)) == NULL )
    return _cmJsonError(p,cmErrLastRC(&p->err),"Unable to create 'bool' array.");
  
  for(i=0; i<n; ++i)
    if((rc = cmJsonCreateBool(h,np,value[i])) != kOkJsRC )
      return _cmJsonError(p,rc,"Unable to create 'bool' array element at index %i.",i);

  return rc;
}


cmJsRC_t    cmJsonSetInt(    cmJsonH_t h, cmJsonNode_t*  np, int         ival )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  if( np->typeId != kIntTId )
    return _cmJsonError(p,kInvalidNodeTypeJsRC,"Cannot assign type 'int' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId));

  np->u.intVal = ival;

  p->modifiedFl = true;
  
  return kOkJsRC;
}

cmJsRC_t    cmJsonSetReal(   cmJsonH_t h, cmJsonNode_t * np, double      rval )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  if( np->typeId != kRealTId )
    return _cmJsonError(p,kInvalidNodeTypeJsRC,"Cannot assign type 'real' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId));

  np->u.realVal = rval;

  p->modifiedFl = true;

  return kOkJsRC;
}

cmJsRC_t    cmJsonSetBool(   cmJsonH_t h, cmJsonNode_t * np, bool        bval )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  if( np->typeId == kTrueTId || np->typeId==kFalseTId )
    return _cmJsonError(p,kInvalidNodeTypeJsRC,"Cannot assign type 'bool' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId));

  np->u.boolVal = bval;

  p->modifiedFl = true;

  return kOkJsRC;  
}

cmJsRC_t    cmJsonSetString( cmJsonH_t h, cmJsonNode_t*  np, const char* sval )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  if( np->typeId != kStringTId )
    return _cmJsonError(p,kInvalidNodeTypeJsRC,"Cannot assign type 'string' to node type '%s'.",_cmJsonNodeTypeIdToLabel(np->typeId));

  unsigned sn = strlen(sval);

  if( np->u.stringVal != NULL && strlen(np->u.stringVal) <= sn )
    strcpy(np->u.stringVal,sval);
  else
    return  _cmJsonSetString(p,np,sval,sn);

  p->modifiedFl = true;
  
  return kOkJsRC;
}

cmJsonNode_t* cmJsonInsertPair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned typeId, const char* sv, int iv, double dv )
{
   assert( objectNodePtr->typeId == kObjectTId );

   cmJsRC_t      rc;
   cmJsonNode_t* pairNodePtr;
 
   cmJs_t* p = _cmJsonHandleToPtr(h);

   if((rc = _cmJsonCreatePair(p,objectNodePtr,label,&pairNodePtr)) != kOkJsRC )
     return NULL;
   
   assert( pairNodePtr != NULL );

   if((rc = cmJsonCreate(h,pairNodePtr,typeId,sv,iv,dv,NULL)) != kOkJsRC )
     return NULL;

   return pairNodePtr;  
} 


cmJsonNode_t* cmJsonInsertPairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return NULL;
  
  return cmJsonCreateObject(h,pairNodePtr);  
}

cmJsonNode_t* cmJsonInsertPairArray(  cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return NULL;
  
  return cmJsonCreateArray(h,pairNodePtr);  
}

cmJsonNode_t* cmJsonInsertPairPair(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* pairLabel )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return NULL;
  
  return cmJsonCreatePair(h,pairNodePtr,pairLabel);  
}



cmJsRC_t      cmJsonInsertPairInt( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, int intVal )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);
  
  return cmJsonCreateInt(h,pairNodePtr,intVal);
}
cmJsRC_t      cmJsonInsertPairReal(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, double realVal )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);
  
  return cmJsonCreateReal(h,pairNodePtr,realVal);
}

cmJsRC_t      cmJsonInsertPairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* stringVal )
{ return cmJsonInsertPairStringN(h,objectNodePtr,label,stringVal,stringVal==NULL ? 0 : strlen(stringVal)); }

cmJsRC_t      cmJsonInsertPairStringN( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, const char* stringVal, unsigned stringCharCnt )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);
  
  return cmJsonCreateStringN(h,pairNodePtr,stringVal,stringCharCnt);
}


cmJsRC_t      cmJsonInsertPairBool(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, bool boolVal )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);
  
  return cmJsonCreateBool(h,pairNodePtr,boolVal);
}

cmJsRC_t      cmJsonInsertPairNull(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);
  
  return cmJsonCreateNull(h,pairNodePtr);
}

cmJsRC_t      cmJsonInsertPairIntArray(    cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const int* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);

  return cmJsonCreateIntArray(h,pairNodePtr,n,values);
}

cmJsRC_t      cmJsonInsertPairRealArray(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const double* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);

  return cmJsonCreateRealArray(h,pairNodePtr,n,values);
}

cmJsRC_t      cmJsonInsertPairStringArray( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const char** values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);

  return cmJsonCreateStringArray(h,pairNodePtr,n,values);
}

cmJsRC_t      cmJsonInsertPairBoolArray(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const bool* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) == NULL )
    return cmJsonErrorCode(h);

  return cmJsonCreateBoolArray(h,pairNodePtr,n,values);

}

cmJsonNode_t*    cmJsonInsertPairIntArray2(    cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const int* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL )
    if( cmJsonCreateIntArray(h,pairNodePtr,n,values) != kOkJsRC )
      return NULL;

  return pairNodePtr;
}

cmJsonNode_t*    cmJsonInsertPairRealArray2(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const double* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL )
    if( cmJsonCreateRealArray(h,pairNodePtr,n,values) != kOkJsRC )
      return NULL;
  return pairNodePtr;
}

cmJsonNode_t*    cmJsonInsertPairStringArray2( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const char** values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL )
    if( cmJsonCreateStringArray(h,pairNodePtr,n,values) != kOkJsRC )
      return NULL;

  return pairNodePtr;
}

cmJsonNode_t*    cmJsonInsertPairBoolArray2(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned n, const bool* values )
{
  assert( objectNodePtr->typeId == kObjectTId );
  cmJsonNode_t* pairNodePtr;
  if((pairNodePtr = cmJsonCreatePair(h,objectNodePtr,label)) != NULL )
    if( cmJsonCreateBoolArray(h,pairNodePtr,n,values) != kOkJsRC )
      return NULL;
  return pairNodePtr;
}


cmJsRC_t  _cmJsonInsertOrReplacePair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned newTypeId, const char* sv, int iv, double dv, cmJsonNode_t* nv, bool insertFl, cmJsonNode_t** retNodePtrPtr )
{
  cmJsRC_t      rc          = kOkJsRC;
  cmJs_t*       p           = _cmJsonHandleToPtr(h);
  cmJsonNode_t* valNodePtr  = NULL;
  cmJsonNode_t* pairNodePtr = NULL;

  assert( objectNodePtr->typeId == kObjectTId );

  if( retNodePtrPtr != NULL )
    *retNodePtrPtr = NULL;

  // if a matching pair was not found  ....
  if(( valNodePtr = cmJsonFindValue(h,label,objectNodePtr,matchTypeMask)) == NULL )
  {
    // ... and insertion is not allowed then return error
    if( insertFl == false )
      return kNodeNotFoundJsRC;

    // ... otherwise insert a new pair and return a pointer to it
    pairNodePtr = cmJsonInsertPair(h,objectNodePtr,label,newTypeId,sv,iv,dv);

    goto errLabel;
   
  }

  // ... otherwise a match was found to at least the pair label.
  // If matchTypeMask was set to kInvalidTId then the type id
  // of the found pair may not be the same as newTypeId. To handle
  // this case we proceed by first deallocating all resources held
  // by the found node and then proceeding by either creating 
  // deleting the found node and creating a replacement node 
  // (object,array,pair) or forcing the found node to a new  
  // type (int,real,true,false,string,null).

  assert( valNodePtr != NULL);

  pairNodePtr = valNodePtr->ownerPtr;

  // release any resources held by the found node
  switch( valNodePtr->typeId )
  {
    case kObjectTId:
    case kArrayTId:
    case kPairTId:

      // remove the pair value node and replace it with a 'null' node.
      if((rc =_cmJsonRemoveNode( p, valNodePtr, true, true )) != kOkJsRC )
        goto errLabel;
      break;

    case kStringTId:
      // deallocate string memory
      _cmJsonSetString( p, valNodePtr, NULL, 0 );
      break;
      
  }
  

  // relace the found node with the new node
  switch( newTypeId )
  {
    case kObjectTId:
    case kArrayTId:
    case kPairTId:
      {
        cmJsonNode_t* newValueNodePtr = NULL;

        // remove the current pair value  ....
        if((rc =_cmJsonRemoveNode( p, valNodePtr, true, false )) != kOkJsRC )
          goto errLabel;


        // new pair nodes should have the pair label in 'sv'
        assert( newTypeId!=kPairTId || (newTypeId==kPairTId && sv != NULL ));

        // if no new value was given or the new value is a pair then ...
        if( nv == NULL || newTypeId == kPairTId )
        {
          // ... create a new blank array,obj or pair
          if((rc = cmJsonCreate( h, pairNodePtr, newTypeId, sv, 0, 0, &newValueNodePtr )) != kOkJsRC )
            goto errLabel;
        }

        // if the new value is a pair and no value node was given then set the
        // new pairs value node to NULL
        if( nv == NULL && newTypeId == kPairTId )
        {
          if((rc = _cmJsonCreateNull(p,newValueNodePtr,NULL)) != kOkJsRC )
            goto errLabel;        
        }
        
        // if a new value node was given  
        if( nv != NULL )
        {
          // the new child node should not already be linked to a parent
          assert( nv->ownerPtr == NULL );

          // if the new value is an obj or array then the new 
          // value node type id should be the same
          assert( newTypeId==kPairTId || newTypeId==nv->typeId );

          // 
          cmJsonNode_t* pp = newTypeId==kPairTId ? newValueNodePtr : pairNodePtr;

          assert( pp->typeId == kPairTId );

          // link in the child to the pair
          if((rc = _cmJsonLinkInChild(p,pp,nv)) != kOkJsRC )
            goto errLabel;

        }
      }
      break;

      // All cases below follow the same pattern:
      // 1. Set the type id to the replacement value type id
      // 2. Assign the value to the node.
      // This sequence is safe because all resources were freed 
      // by the earlier switch.

    case kStringTId:
      valNodePtr->typeId = kStringTId;
      _cmJsonSetString( p, valNodePtr, sv, strlen(sv) );
      break;

    case kIntTId:
      valNodePtr->typeId =kIntTId;
      valNodePtr->u.intVal = iv;
      break;

    case kRealTId:
      valNodePtr->typeId = kRealTId;
      valNodePtr->u.realVal = dv;
      break;

    case kTrueTId:
      valNodePtr->typeId = kTrueTId;
      valNodePtr->u.boolVal = true;
      break;

    case kFalseTId:
      valNodePtr->typeId = kFalseTId;
      valNodePtr->u.boolVal = false;
      break;

    case kNullTId:
      valNodePtr->typeId = kNullTId;
      break;
    
    default:
      { assert(0); }

  }

 errLabel:
  
  if( rc == kOkJsRC )
  {
    if( retNodePtrPtr != NULL )
      *retNodePtrPtr = pairNodePtr;

    p->modifiedFl = true;

  }
  return rc;
}

cmJsonNode_t* cmJsonInsertOrReplacePair( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned typeId, const char* sv, int iv, double dv, cmJsonNode_t* nv )
{  
  cmJsonNode_t* retNodePtr = NULL;

  _cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,typeId,sv,iv,dv,nv,true,&retNodePtr ); 

  return retNodePtr;
}

cmJsonNode_t* cmJsonInsertOrReplacePairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newObjNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kObjectTId,NULL,0,0,newObjNodePtr)) == NULL )
    return NULL;

  return pairPtr->u.childPtr->siblingPtr;
}

cmJsonNode_t* cmJsonInsertOrReplacePairArray(  cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newArrayNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kArrayTId,NULL,0,0,newArrayNodePtr)) == NULL )
    return NULL;

  return pairPtr->u.childPtr->siblingPtr;
}

cmJsonNode_t* cmJsonInsertOrReplacePairPair(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* newPairLabel, cmJsonNode_t* newPairValNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kPairTId,newPairLabel,0,0,newPairValNodePtr)) == NULL )
    return NULL;

  return pairPtr->u.childPtr->siblingPtr;
}

cmJsRC_t      cmJsonInsertOrReplacePairInt(    cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, int intVal )
{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kIntTId,NULL,intVal,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonInsertOrReplacePairReal(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, double realVal )
{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kRealTId,NULL,0,realVal,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonInsertOrReplacePairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* stringVal )
{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kStringTId,stringVal,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonInsertOrReplacePairBool(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, bool boolVal )
{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,boolVal ? kTrueTId : kFalseTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonInsertOrReplacePairNull(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask )
{ return cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,kNullTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) :  kOkJsRC;   }

cmJsonNode_t* cmJsonReplacePair(       cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, unsigned newTypeId, const char* sv, int iv, double dv, cmJsonNode_t* nv )
{
  cmJsonNode_t* retNodePtr = NULL;
  _cmJsonInsertOrReplacePair(h,objectNodePtr,label,matchTypeMask,newTypeId,sv,iv,dv,nv,false,&retNodePtr ); 
  return retNodePtr;
}

cmJsonNode_t* cmJsonReplacePairObject( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newPairNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kObjectTId,NULL,0,0,newPairNodePtr)) == NULL )
    return NULL;
  return pairPtr->u.childPtr->siblingPtr;
}


cmJsonNode_t* cmJsonReplacePairArray(  cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, cmJsonNode_t* newArrayNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kArrayTId,NULL,0,0,newArrayNodePtr)) == NULL )
    return NULL;
  return pairPtr->u.childPtr->siblingPtr;
}

cmJsonNode_t* cmJsonReplacePairPair(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* newPairLabel, cmJsonNode_t* newPairValNodePtr )
{
  cmJsonNode_t* pairPtr;
  if((pairPtr = cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kPairTId,newPairLabel,0,0,newPairValNodePtr)) == NULL )
    return NULL;
  return pairPtr->u.childPtr->siblingPtr;
}


cmJsRC_t      cmJsonReplacePairInt(    cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, int intVal )
{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kIntTId,NULL,intVal,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; }

cmJsRC_t      cmJsonReplacePairReal(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, double realVal )
{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kRealTId,NULL,0,realVal,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC; }

cmJsRC_t      cmJsonReplacePairString( cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, const char* stringVal )
{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kStringTId,stringVal,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonReplacePairBool(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask, bool boolVal )
{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,boolVal ? kTrueTId : kFalseTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) : kOkJsRC;   }

cmJsRC_t      cmJsonReplacePairNull(   cmJsonH_t h, cmJsonNode_t* objectNodePtr, const char* label, unsigned matchTypeMask )
{ return cmJsonReplacePair(h,objectNodePtr,label,matchTypeMask,kNullTId,NULL,0,0,NULL) == NULL ? cmJsonErrorCode(h) :  kOkJsRC;   }

cmJsRC_t      cmJsonVInsertPairs( cmJsonH_t h, cmJsonNode_t* objNodePtr, va_list vl )
{
  cmJsRC_t rc = kOkJsRC;

  assert( objNodePtr->typeId == kObjectTId );

  const char* label;

  while( ((label = va_arg(vl,const char*)) != NULL) && (rc == kOkJsRC) )
  {
    unsigned sel = va_arg(vl,unsigned);
    switch( sel )
    {
      case kObjectTId:
        if( cmJsonInsertPairObject(h,objNodePtr,label) == NULL )
          rc = cmJsonErrorCode(h);
        break;

      case kArrayTId:
        if( cmJsonInsertPairArray(h,objNodePtr,label) == NULL )
          rc = cmJsonErrorCode(h);
        break;

      case kPairTId:
        if( cmJsonInsertPairPair(h,objNodePtr,label, va_arg(vl,const char*)) == NULL )
          rc = cmJsonErrorCode(h);
        break;


      case kIntTId:
        rc = cmJsonInsertPairInt(h,objNodePtr,label, va_arg(vl,int) );
        break;

      case kRealTId:
        rc = cmJsonInsertPairReal(h,objNodePtr,label, va_arg(vl,double) );
        break;

      case kStringTId:
        rc = cmJsonInsertPairString(h,objNodePtr,label, va_arg(vl,const char *) );
        break;

      case kTrueTId:
      case kFalseTId:
      case kBoolTId:
        rc = cmJsonInsertPairBool(h,objNodePtr,label, va_arg(vl,int) );
        break;

      case kNullTId:
        rc = cmJsonInsertPairNull(h,objNodePtr,label );
        break;

      default:
        // missing terminating NULL on the var args list???
        assert(0);
        break;
    }
  }

  return rc;
}    

cmJsRC_t      cmJsonInsertPairs( cmJsonH_t h, cmJsonNode_t* objectNodePtr, ... )
{
  va_list vl;
  va_start(vl,objectNodePtr);
  cmJsRC_t rc = cmJsonVInsertPairs(h,objectNodePtr,vl);
  va_end(vl);
  return rc;
}

cmJsonNode_t* cmJsonVCreateFilledObject( cmJsonH_t h, cmJsonNode_t* parentPtr, va_list vl )
{
  cmJsonNode_t* np;

  if((np = cmJsonCreateObject(h,parentPtr)) == NULL)
    return NULL;

  if( cmJsonVInsertPairs(h,np,vl) != kOkJsRC )
  {
    cmJsonRemoveNode(h,np,true);
    return NULL;
  }

  return np;  
}

cmJsonNode_t* cmJsonCreateFilledObject( cmJsonH_t h, cmJsonNode_t* parentPtr, ... )
{
  va_list vl;
  va_start(vl,parentPtr);
  cmJsonNode_t* np = cmJsonVCreateFilledObject(h,parentPtr,vl);
  va_end(vl);
  return np;
}

void _cmJsonFreeNode( cmJs_t* p, cmJsonNode_t* np )
{
  switch( np->typeId )
  {
    case kObjectTId:
    case kPairTId:
    case kArrayTId:
      {
        cmJsonNode_t* cnp = np->u.childPtr;
        while( cnp != NULL )
        {
          cmJsonNode_t* nnp = cnp->siblingPtr;
          _cmJsonFreeNode(p,cnp);
          cnp = nnp;
        }

      }
      break;

    case kStringTId:
      if( np->u.stringVal != NULL )
        cmLHeapFree(p->heapH,np->u.stringVal);
      break;


  }

  cmLHeapFree(p->heapH,np);
    
}

cmJsRC_t _cmJsonRemoveNode( cmJs_t* p, cmJsonNode_t* np, bool freeFl, bool balancePairsFl )
{
  if(np == NULL )
    return kOkJsRC;

  cmJsonNode_t* parentPtr = np->ownerPtr;

  // if np is the root ...
  if( np == p->rootPtr )
  {
    //  ... we only need to set the root to null to remove the node.
    p->rootPtr = NULL;
  }
  else
  {
    if( parentPtr != NULL )
    {
      // get the parents first child
      cmJsonNode_t* cnp = parentPtr->u.childPtr;

      // if np is the first child then make the second child the first child
      if( cnp == np )
      {
        if( parentPtr->typeId == kPairTId )
          return _cmJsonError( p, kCannotRemoveLabelJsRC, "Cannot remove pair label nodes because this would invalidate the tree structure.");

        parentPtr->u.childPtr = cnp->siblingPtr;
      }
      else
      {
        // otherwise unlink it from the child chain
        while( cnp != NULL )    
        {
          if( cnp->siblingPtr == np )
          {

            cnp->siblingPtr = np->siblingPtr;

            // if the parent is a pair then the removed node is a
            // 'pair value' which must be replaced with a null node in order
            // to maintain a valid tree.
            if( (parentPtr->typeId == kPairTId) && balancePairsFl)
              _cmJsonCreateNull( p, parentPtr, NULL );

            break;
          }

          cnp = cnp->siblingPtr;
        }

        assert( cnp != NULL );        
      }
    }
  }

  // if the memory assoc'd with the removed node should be released
  if( freeFl )
  {
    _cmJsonFreeNode(p,np);
    /*
    if( np->typeId == kStringTId )
    {
      cmLHeapFree(p->heapH,np->u.stringVal);
      np->u.stringVal = NULL;
    }

    cmLHeapFree(p->heapH,np);
    */
  } 

  p->modifiedFl = true;

  return kOkJsRC;
}


cmJsRC_t cmJsonRemoveNode( cmJsonH_t h, cmJsonNode_t* np, bool freeFl )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  return _cmJsonRemoveNode( p, np, freeFl, true );
}

cmJsRC_t _cmJsonValidateErr( cmJs_t* p, const char* text )
{
  if( p != NULL )
    return _cmJsonSyntaxError(p,text);

  return kValidateFailJsRC;
}

// 'p' is optional.  If 'p' is set to NULL the function will return kValidFailJsRC if the 
// tree rooted at 'np' is invalid and will not print an error message.
cmJsRC_t _cmJsonValidateNode( cmJs_t* p, const cmJsonNode_t* np, const cmJsonNode_t* parentPtr )
{
  cmJsRC_t rc = kOkJsRC;

  if( parentPtr != np->ownerPtr )
    return _cmJsonValidateErr(p,"A child->parent link does not agree with a parent->child link.");

  if( parentPtr == NULL )
  {
    if( np->typeId != kArrayTId && np->typeId != kObjectTId )
      return _cmJsonValidateErr(p,"Only 'array' and 'object' nodes may be the root element.");
  }
  else
  {

    if( parentPtr->typeId != kArrayTId && parentPtr->typeId != kObjectTId && parentPtr->typeId != kPairTId )
      return _cmJsonValidateErr(p,"Parent nodes must be either 'object','array' or 'pair' nodes.");
  }  
  

  switch( np->typeId )
  {
    case kPairTId:
      if( cmJsonChildCount(np) != 2 )
        return _cmJsonValidateErr(p,"'pair' nodes must have exactly two children.");

      if( np->u.childPtr->typeId != kStringTId )
        return _cmJsonValidateErr(p,"The first child of 'pair' nodes must be a 'string' node.");

      // fall through


    case kObjectTId:
    case kArrayTId:
      {
        // validate each child node
        cmJsonNode_t* cnp = np->u.childPtr;
        while(cnp != NULL)
        {
          if( cnp->ownerPtr != np )
            return _cmJsonValidateErr(p,"A parent->child pointer was not validated with a child->parent pointer.");

          if( np->typeId == kObjectTId && cnp->typeId != kPairTId )
            return _cmJsonValidateErr(p,"All 'object' child nodes must be 'pair' nodes.");

          if((rc = _cmJsonValidateNode(p,cnp,np)) != kOkJsRC )
            return rc;

          cnp = cnp->siblingPtr;
        }
      }
      break;


    case kStringTId:
    case kIntTId:
    case kRealTId:
    case kNullTId:
    case kTrueTId:
    case kFalseTId:
    default:
      break;

  }

  return rc;
}

cmJsRC_t      cmJsonValidateTree( cmJsonH_t h )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  return _cmJsonValidateNode(p,p->rootPtr,NULL);
}

cmJsRC_t      cmJsonValidate( const cmJsonNode_t* np )
{ return _cmJsonValidateNode(NULL,np,np->ownerPtr); }


cmJsonNode_t* _cmJsonDuplicateNode( cmJs_t* p, const cmJsonNode_t* np, cmJsonNode_t* parentPtr )
{
  cmJsonNode_t* newParentPtr = NULL;
  cmJsonNode_t* newNodePtr   = NULL;
  cmJsRC_t      rc           = kOkJsRC;

  switch( np->typeId )
  {
    case kObjectTId:
    case kArrayTId:
      rc = _cmJsonCreateNode(p,parentPtr,np->typeId,&newParentPtr);
      break;

    case kPairTId:
      rc = _cmJsonCreatePair(p,parentPtr,np->u.childPtr->u.stringVal,&newParentPtr);
      break;

    case kIntTId:
      rc = _cmJsonCreateInt(p,parentPtr,np->u.intVal,&newNodePtr);
      break;

    case kRealTId:
      rc = _cmJsonCreateReal(p,parentPtr,np->u.realVal,&newNodePtr);
      break;

    case kStringTId:
      rc = _cmJsonCreateString(p,parentPtr,np->u.stringVal,strlen(np->u.stringVal),&newNodePtr);
      break;

    case kTrueTId:
    case kFalseTId:
      rc = _cmJsonCreateBool(p,parentPtr,np->u.boolVal,&newNodePtr);
      break;

    case kNullTId:
      rc = _cmJsonCreateNull(p,parentPtr,&newNodePtr);
  }

  if( rc != kOkJsRC )
    return NULL;

  if( newParentPtr != NULL )
  {
    newNodePtr = newParentPtr;

    cmJsonNode_t* cnp = np->u.childPtr;
    
    if(np->typeId == kPairTId)
      cnp = np->u.childPtr->siblingPtr;

    while( cnp != NULL )
    {
      if( _cmJsonDuplicateNode(p,cnp,newParentPtr) != kOkJsRC )
        return NULL;

      cnp = cnp->siblingPtr;
    }
  }

  return newNodePtr;

}

cmJsonNode_t* cmJsonDuplicateNode( cmJsonH_t h, const cmJsonNode_t* np, cmJsonNode_t* parentPtr )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);

  assert( _cmJsonValidateNode(p,np,NULL) == kOkJsRC );

  return _cmJsonDuplicateNode(p,np,parentPtr);
}


cmJsRC_t cmJsonMergeObjectNodes( cmJsonH_t h, cmJsonNode_t* dstObjNodePtr, const cmJsonNode_t* srcObjNodePtr )
{
  assert( dstObjNodePtr!=NULL && dstObjNodePtr->typeId == kObjectTId );
  assert( srcObjNodePtr!=NULL && srcObjNodePtr->typeId == kObjectTId );

  cmJsRC_t            rc  = kOkJsRC;
  cmJs_t*             p   = _cmJsonHandleToPtr(h);
  cmJsonNode_t*       cnp = NULL;
  const cmJsonNode_t* snp = srcObjNodePtr->u.childPtr;

  while( snp!=NULL && rc==kOkJsRC )
  {
    cmJsonNode_t* dnp;

    assert( snp->typeId == kPairTId );

    // if the src pair was not found in the dst object ...
    if((rc = _cmJsonFindMemberValue(dstObjNodePtr, cmJsonPairLabel(snp), snp->u.childPtr->siblingPtr->typeId, &dnp )) != kOkJsRC )
    {
      cmJsonNode_t* newPairNodePtr;

      // the only acceptable error is kNodeNotFoundJsRC 
      // (in particular we reject kNodeCannotCvtJsRC to avoid duplicating
      // pairs with the same name but different types)
      if( rc != kNodeNotFoundJsRC )
        goto errLabel;

      // create the new pair and attach it to the dst obj node
      if((rc = _cmJsonCreatePair(p,dstObjNodePtr,cmJsonPairLabel(snp),&newPairNodePtr)) != kOkJsRC )
        goto errLabel;

      // duplicate the src pair value node and attcmh it to the new dst node
      if(  _cmJsonDuplicateNode(p,snp->u.childPtr->siblingPtr,newPairNodePtr) == NULL )
        rc = p->rc;

      // set kTempJsFl on the new node to use possible cleanup on error later
      newPairNodePtr->typeId = cmSetFlag(newPairNodePtr->typeId,kTempJsFl);

    }


    snp = snp->siblingPtr;
          
  }

 errLabel:

  // for each dst obj pair
  cnp  = dstObjNodePtr->u.childPtr;

  while( cnp != NULL)
  {
    // if this child is a new node
    if( cmIsFlag(cnp->typeId,kTempJsFl) )
    {
      // clear the temp fl
      cnp->typeId = cmClrFlag(cnp->typeId,kTempJsFl);

      // if there was an error remove all new pairs
      if( rc != kOkJsRC )
        cmJsonRemoveNode(h,cnp,true);
    }

    cnp = cnp->siblingPtr;
  }

  return rc;
}


void _cmJsonSerialCopy( cmJsSerial_t* rp, const void* sp, unsigned sn )
{
  assert( rp->nextPtr + sn <= rp->endPtr );

  memcpy(rp->nextPtr,sp,sn);

  rp->nextPtr += sn;
}

cmJsRC_t _cmJsonSerializeNode( const cmJsonNode_t* np, cmJsSerial_t* rp )
{
  cmJsRC_t rc = kOkJsRC;

  // on the first pass rp->basePtr will be NULL so collect size information
  // on the second pass rp->basePtr will be set so copy out data

  // write the type id of this node
  if( rp->basePtr != NULL )
    _cmJsonSerialCopy(rp,&np->typeId,sizeof(np->typeId));
  else
    rp->hdr.byteCnt += sizeof(np->typeId);

  rp->hdr.nodeCnt++;

  switch( np->typeId )
  {

    // write the child count
    case kObjectTId:
    case kArrayTId:
      if( rp->basePtr == NULL )
        rp->hdr.byteCnt += sizeof(unsigned);
      else
      {

        unsigned n = cmJsonChildCount( np );

        _cmJsonSerialCopy(rp,&n,sizeof(unsigned));
      }
      // fall through

    case kPairTId:        
      {
        cmJsonNode_t* cnp = np->u.childPtr;
        while(cnp != NULL )
        {
          _cmJsonSerializeNode(cnp,rp);
          cnp = cnp->siblingPtr;
        }
      }
      break;

    case kStringTId:
      // write the string contents
      if( rp->basePtr == NULL )
        rp->hdr.byteCnt += strlen(np->u.stringVal) + 1;
      else
        _cmJsonSerialCopy( rp, np->u.stringVal, strlen(np->u.stringVal)+1 );

      break;

    case kIntTId:
      // write the int value
      if( rp->basePtr == NULL )
        rp->hdr.byteCnt += sizeof(np->u.intVal);
      else
        _cmJsonSerialCopy(rp,&np->u.intVal,sizeof(np->u.intVal));
      break;

    case kRealTId:
      // write the real value
      if( rp->basePtr == NULL )
        rp->hdr.byteCnt += sizeof(np->u.realVal);
      else
        _cmJsonSerialCopy(rp, &np->u.realVal, sizeof(np->u.realVal));
      break;
  }

  return rc;

}

unsigned      cmJsonSerialByteCount( const cmJsonNode_t* np )
{
  cmJsRC_t rc;
  cmJsSerial_t vr;
  memset(&vr,0,sizeof(vr));

  // make a first pass to determine the size of the buffer
  if((rc = _cmJsonSerializeNode(np,&vr)) != kOkJsRC )
    return rc;

  // increment the buffer size to include the buffer header
  return  (4*sizeof(unsigned)) + vr.hdr.byteCnt;
}

cmJsRC_t      cmJsonSerialize(  const cmJsonNode_t* np, void* buf, unsigned bufByteCnt )
{
  cmJsRC_t     rc = kOkJsRC;
  cmJsSerial_t vr;
  memset(&vr,0,sizeof(vr));

    // setup the serial buffer
  vr.hdr.id      = 'json';
  vr.hdr.byteCnt = bufByteCnt - (2*sizeof(unsigned));
  vr.hdr.version = 0;
  vr.basePtr     = buf;
  vr.nextPtr     = vr.basePtr;
  vr.endPtr      = vr.basePtr + bufByteCnt;

  // write the header recd
  _cmJsonSerialCopy(&vr, &vr.hdr.id,      sizeof(unsigned));  
  _cmJsonSerialCopy(&vr, &vr.hdr.byteCnt, sizeof(unsigned));  
  _cmJsonSerialCopy(&vr, &vr.hdr.nodeCnt, sizeof(unsigned));  
  _cmJsonSerialCopy(&vr, &vr.hdr.version, sizeof(unsigned));  

  // copy the node data into the serial buffer
  if((rc = _cmJsonSerializeNode(np,&vr)) != kOkJsRC )
    return rc;

  vr.basePtr = buf;
  vr.nextPtr = vr.basePtr;
  _cmJsonSerialCopy(&vr, &vr.hdr.id,      sizeof(unsigned));  
  _cmJsonSerialCopy(&vr, &vr.hdr.byteCnt, sizeof(unsigned));  
  _cmJsonSerialCopy(&vr, &vr.hdr.nodeCnt, sizeof(unsigned));  

  return rc;

}

cmJsRC_t      cmJsonSerializeTree( cmJsonH_t h, const cmJsonNode_t* np, void** bufPtrPtr, unsigned* bufByteCntPtr)
{
  cmJsRC_t rc;
  cmJsSerial_t vr;
  memset(&vr,0,sizeof(vr));

  cmJs_t* p      = _cmJsonHandleToPtr(h);

  assert( bufPtrPtr != NULL && bufByteCntPtr != NULL );

  *bufPtrPtr     = NULL;
  *bufByteCntPtr = 0;

  if( np == NULL )
    np = p->rootPtr;
   
  // validate the tree
  if((rc = _cmJsonValidateNode(p,np,np->ownerPtr)) != kOkJsRC )
    return rc;

  // increment the buffer size to include the buffer header
  p->serialByteCnt = cmJsonSerialByteCount(np);

  // allocate the serial buffer memory
  p->serialBufPtr  = cmMemResize( char, p->serialBufPtr, p->serialByteCnt );

  // serialize the tree
  if((rc = cmJsonSerialize(np, p->serialBufPtr, p->serialByteCnt )) != kOkJsRC )
    return rc;

  *bufPtrPtr     = p->serialBufPtr;
  *bufByteCntPtr = p->serialByteCnt;

  return rc;
}

const void* _cmJsonDeserialAdv( cmJsDeserial_t* rp, unsigned n )
{
  const void* vp = rp->nextPtr;
  rp->nextPtr += n;
  assert(rp->nextPtr <= rp->endPtr);
  assert(rp->nodeIdx <= rp->nodeCnt);
  return vp;
}

int _cmJsonDeserialInt( cmJsDeserial_t* rp )
{ return *(const int*)_cmJsonDeserialAdv(rp,sizeof(int)); }

unsigned _cmJsonDeserialUint( cmJsDeserial_t* rp )
{ return *(const unsigned*)_cmJsonDeserialAdv(rp,sizeof(unsigned)); }

double _cmJsonDeserialReal( cmJsDeserial_t* rp )
{ return *(const double*)_cmJsonDeserialAdv(rp,sizeof(double)); }


cmJsRC_t _cmJsonDeserializeNode( cmJs_t* p, cmJsonNode_t* parentPtr, cmJsDeserial_t* rp )
{
  cmJsRC_t      rc           = kOkJsRC;
  unsigned      typeId       = _cmJsonDeserialUint(rp);
  unsigned      childCnt     = 0;
  cmJsonNode_t* newParentPtr = NULL;  

  rp->nodeIdx++;
  
  switch( typeId )
  {
    case kPairTId:
      rc       = _cmJsonCreateNode(p,parentPtr,typeId,&newParentPtr);
      childCnt = 2;
      break;

    case kObjectTId:
    case kArrayTId:
      rc       = _cmJsonCreateNode(p,parentPtr,typeId,&newParentPtr);
      childCnt = _cmJsonDeserialUint(rp);
      break;

    case kStringTId:
      {
        unsigned sn = strlen(rp->nextPtr);
        rc =  _cmJsonCreateString( p, parentPtr, rp->nextPtr, sn,NULL);
        _cmJsonDeserialAdv(rp,sn+1);
      }
      break;

    case kIntTId: 
      {
        int v = _cmJsonDeserialInt(rp);
        rc = _cmJsonCreateInt( p, parentPtr, v, NULL );
      }
      break;

    case kRealTId:
      {
        double v = _cmJsonDeserialReal(rp);
        rc = _cmJsonCreateReal( p, parentPtr, v, NULL );
      }
      break;

    case kNullTId:
      rc = _cmJsonCreateNull( p, parentPtr, NULL );
      break;

    case kTrueTId:
      rc = _cmJsonCreateBool( p, parentPtr, true, NULL );
      break;

    case kFalseTId:
      rc = _cmJsonCreateBool( p, parentPtr, false, NULL );
      break;

    default:
      assert(0);
      break;
  }

  if( rc != kOkJsRC )
    return rc;

  // if the current node is a parent
  if( childCnt > 0 )
  {
    unsigned i;
    assert( newParentPtr != NULL );
    
    for(i=0; i<childCnt; ++i)
      if((rc= _cmJsonDeserializeNode( p, newParentPtr, rp )) != kOkJsRC )
        return rc;
  }

  return rc;

}

cmJsRC_t _cmJsonDeserialize( cmJs_t* p, const void* vbuf, cmJsonNode_t* altRootPtr )
{
  cmJsDeserial_t         r;
  cmJsonNode_t*          rootPtr = altRootPtr == NULL ? p->rootPtr : altRootPtr;
  const char*            buf     = (const char*)vbuf;

  memset(&r,0,sizeof(r));

  r.nextPtr = buf;
  r.endPtr  = buf + (4*sizeof(unsigned)); // the buf must at least contain a header

  // read the buffer header
  unsigned hdrId   = _cmJsonDeserialUint(&r);
  unsigned byteCnt = _cmJsonDeserialUint(&r);
  r.nodeCnt        = _cmJsonDeserialUint(&r);
  /*unsigned version =*/ _cmJsonDeserialUint(&r);
  
  if( hdrId != 'json' )
    return _cmJsonError(p,kSerialErrJsRC,"The buffer does not have the correct header.");

  if( byteCnt < (4*sizeof(unsigned)) )
    return _cmJsonError(p,kSerialErrJsRC,"The buffer is too small to be contain any information.");

  // change the buffer end pointer to the correct size based
  // on the the byte count stored in the buffer
  r.endPtr = buf + byteCnt + (2*sizeof(unsigned));

  return _cmJsonDeserializeNode(p, rootPtr, &r );   

} 

cmJsRC_t      cmJsonDeserialize( cmJsonH_t h, const void* bufPtr, cmJsonNode_t* altRootPtr )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return _cmJsonDeserialize(p,bufPtr,altRootPtr);
}

cmJsRC_t      cmJsonLeafToString( const cmJsonNode_t* np, cmChar_t* buf, unsigned bufCharCnt )
{
  const char* cp = NULL;
  unsigned    n  = 0;

  assert( buf!=NULL && bufCharCnt > 0 );

  switch(np->typeId & kMaskTId )
  {
    case kStringTId:      
      cp = np->u.stringVal==NULL ? "" : np->u.stringVal;
      break;

    case kNullTId:
      cp = "null";
      break;

    case kTrueTId:
      cp = "true";
      break;

    case kFalseTId:
      cp = "false";
      break;
    
    case kIntTId:
      n = snprintf(buf,bufCharCnt,"%i",np->u.intVal)+1;
      break;

    case kRealTId:
      n = snprintf(buf,bufCharCnt,"%f",np->u.realVal)+1;
      break;


    default:
      assert(0); 
      return kInvalidNodeTypeJsRC;

  }

  if( cp != NULL )
  {
    n = strlen(cp)+1;

    if( bufCharCnt < n )
      n  = bufCharCnt;

    strncpy(buf,cp,n);

    /*
    n = strlen(np->u.stringVal)+1;

    if( bufCharCnt < n )
      n  = bufCharCnt;

    strncpy(buf,np->u.stringVal,n);
    */
  }

  buf[bufCharCnt-1]=0;

  assert( n>0 && n < bufCharCnt );

  return n == bufCharCnt ? kBufTooSmallJsRC : kOkJsRC ;
}


cmJsRC_t _cmJsonRptCsvTokErr( cmJs_t* p, unsigned lineNo, cmLexH lexH, const char* iFn )
{
  unsigned n = cmMin(31,cmLexTokenCharCount(lexH));
  char b[n+1];
  strncpy(b,cmLexTokenText(lexH),n);
  b[n]=0;
  return _cmJsonError( p, kCsvErrJsRC, "Unexpected token '%s' during CSV parse on line %i of '%s'.",b,cmLexCurrentLineNumber(lexH),cmStringNullGuard(iFn));

}

cmJsRC_t _cmJsonRptCsvTypeErr( cmJs_t* p, unsigned lineNo, cmLexH lexH, const char* iFn, const char* actualTypeStr, unsigned expTypeId )
{
  unsigned n = cmMin(31,cmLexTokenCharCount(lexH));
  char b[n+1];
  strncpy(b,cmLexTokenText(lexH),n);
  b[n]=0;
  return _cmJsonError( p, kCsvErrJsRC, "Unexpected token '%s' during CSV parse on line %i of '%s'.\nExpected type:%s actual type:%s",b,cmLexCurrentLineNumber(lexH),cmStringNullGuard(iFn),_cmJsonNodeTypeIdToLabel(expTypeId),actualTypeStr);
}



cmJsRC_t      cmJsonFromCSV(  cmJsonH_t h, const char* iFn, cmJsonNode_t* parentNodePtr, cmJsonNode_t** arrayNodePtrPtr )
{
  enum
  {
    kCommaTokId = kUserLexTId+1,
    kIntTokId,
    kRealTokId,
    kTrueTokId,
    kFalseTokId,
    kStringTokId,
    kBoolTokId
  };

  typedef struct field_str
  {
    char*             fieldLabel;
    unsigned          typeId;
    struct field_str* linkPtr;
  } field_t;

  cmJsRC_t      rc           = kOkJsRC;
  cmJs_t*       p            = _cmJsonHandleToPtr(h);
  cmLexH        lexH         = cmLexInit( NULL, 0, 0, p->err.rpt );
  field_t*      fieldList    = NULL;
  cmJsonNode_t* arrayNodePtr = NULL;
  cmJsonNode_t* objNodePtr   = NULL;
  unsigned      lineNo       = 0;
  field_t*      fieldPtr     = NULL;
  field_t*      lp           = NULL;
  unsigned      tokId;
  unsigned      fieldIdx     = 0;

  // validate the init state of the lexer
  if( cmLexIsValid(lexH) == false )
  {
    rc = _cmJsonError( p, kLexErrJsRC, "Lexer initialization failed on CSV parse of '%s'.",cmStringNullGuard(iFn));
    goto errLabel;
  }

  // register CSV specific tokens
  cmLexRegisterToken( lexH, kCommaTokId,  ",");
  cmLexRegisterToken( lexH, kIntTokId,    "int");
  cmLexRegisterToken( lexH, kRealTokId,   "real");
  cmLexRegisterToken( lexH, kTrueTokId,   "true");
  cmLexRegisterToken( lexH, kFalseTokId,  "false");
  cmLexRegisterToken( lexH, kStringTokId, "string");
  cmLexRegisterToken( lexH, kBoolTokId,   "bool");

  // lex the file
  if( cmLexSetFile( lexH, iFn ) != kOkLexRC )
  {
    rc = _cmJsonError( p, kLexErrJsRC, "Lex failed on CSV parse of '%s'.",cmStringNullGuard(iFn));
    goto errLabel;
  }

  // create the parent array
  if((arrayNodePtr = cmJsonCreateArray( h, parentNodePtr )) == NULL )
  {
    rc = _cmJsonError( p, kCsvErrJsRC, "CSV array create failed during parse of '%s'.",cmStringNullGuard(iFn));
    goto errLabel;
  }

  // iterate through the lexer file
  while(((tokId = cmLexGetNextToken(lexH)) != kErrorLexTId) && (tokId != kEofLexTId) )
  {
    unsigned fieldTypeTokId = kInvalidTId;

    switch( cmLexCurrentLineNumber(lexH) )
    {
      // line 1 contains the field type labels (e.g. int,real,string,true,false,bool)
      case 1:
        switch(tokId)
        {
          case kCommaTokId:
            break;

          case kIntTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kIntTId : fieldTypeTokId;

          case kRealTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kRealTId : fieldTypeTokId;

          case kTrueTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kTrueTId : fieldTypeTokId;

          case kFalseTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kFalseTId : fieldTypeTokId;

          case kBoolTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kFalseTId : fieldTypeTokId;

          case kStringTokId:
            fieldTypeTokId = (fieldTypeTokId==kInvalidTId) ? kStringTId : fieldTypeTokId;

            // create and intitialize a new field 
            field_t* rp = cmMemAllocZ( field_t, 1 );

            rp->fieldLabel = NULL;
            rp->typeId     = fieldTypeTokId;
            rp->linkPtr    = NULL;

            // and add it to the end of the field list
            if( fieldList == NULL )
              fieldList = rp;
            else
              fieldPtr->linkPtr = rp;

            // fieldPtr points to the end of the list
            fieldPtr = rp;

            break;

          default:
            rc =  _cmJsonRptCsvTokErr( p, 1, lexH, iFn );
            goto errLabel;
            
        }
        break;
        
        // line 2 contains the field labels
      case 2:        
        if( fieldIdx == 0 )
          fieldPtr = fieldList;
        ++fieldIdx;

        switch(tokId)
        {
          case kCommaTokId:
            break;

            // all line 2 tokens must be identifiers or q-strings
          case kIdentLexTId:
          case kQStrLexTId:
            if( fieldPtr == NULL )
            {
              rc = _cmJsonError( p, kCsvErrJsRC, "More fields on line 2 than type specifiers on line 1 of '%s'.",cmStringNullGuard(iFn));
              goto errLabel;
            }
            else
            {
              // set the field name in the field list
              unsigned n              = cmLexTokenCharCount(lexH);
              fieldPtr->fieldLabel    = cmMemAllocZ( char, n+1 );
              strncpy(fieldPtr->fieldLabel,cmLexTokenText(lexH),n);
              fieldPtr->fieldLabel[n] = 0;
              fieldPtr                = fieldPtr->linkPtr;
            }
            break;

          default:
            rc =  _cmJsonRptCsvTokErr( p, 2, lexH, iFn );
            goto errLabel;

        }
        break;

        // lines 3 to end of file contain data
      default:
        {
          int      ival = 0;
          cmReal_t rval = 0;
          
          //  if we are starting a new line in the CSV file
          if( lineNo != cmLexCurrentLineNumber(lexH) )
          {
            // verify that field ptr is pointing to the end of the field list
            if( fieldPtr != NULL )
            {
              rc = _cmJsonError( p, kCsvErrJsRC, "Missing columns were detected on line %i in CSV file '%s'.", lineNo, cmStringNullGuard(iFn));
              goto errLabel;
            }
            
            fieldPtr = fieldList;
            lineNo   = cmLexCurrentLineNumber(lexH);

            // create the object to hold the fields on this line
            if((objNodePtr = cmJsonCreateObject( h, arrayNodePtr )) == NULL )
            {
              rc = _cmJsonError( p, kCsvErrJsRC, "Object node create failed on line %i in CSV file '%s'.",lineNo,cmStringNullGuard(iFn));
              goto errLabel;
            } 
          }

          if( tokId == kCommaTokId )
            continue;

          if( fieldPtr == NULL  )
          {
            rc = _cmJsonError( p, kCsvErrJsRC, "More columns than fields on line %i in CSV file '%s'.", lineNo,cmStringNullGuard(iFn));
            goto errLabel;
          }

          // given the tokens type convert the token string into a value 
          switch(tokId)
          {

            case kRealLexTId:
#ifdef CM_FLOAT_REAL
              rval = cmLexTokenFloat(lexH);
#else
              rval = cmLexTokenDouble(lexH);
#endif
              ival = (int)rval;

              if( fieldPtr->typeId == kStringTId )
              {
                rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "numeric", fieldPtr->typeId );
                goto errLabel;

              }
              break;

            case kIntLexTId:
            case kHexLexTId:
              ival = cmLexTokenInt(lexH);
              rval = ival;
              if( fieldPtr->typeId == kStringTId )
              {
                rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "numeric", fieldPtr->typeId );
                goto errLabel;

              }
              break;

            case kTrueTokId:
              ival = 1;
              rval = 1.0;
              break;

            case kFalseTokId:
              ival = 0;
              rval = 0.0;
              break;

            case kIdentLexTId:
            case kQStrLexTId:
              if( fieldPtr->typeId != kStringTId )
              {
                rc = _cmJsonRptCsvTypeErr(p, lineNo, lexH, iFn, "string", fieldPtr->typeId );
                goto errLabel;
              }
              break;

            default:
              rc =  _cmJsonRptCsvTokErr( p, lineNo, lexH, iFn );
              goto errLabel;
          }


          // create the pair object from the current field label and value
          switch(fieldPtr->typeId)
          {
            case kIntTId:
              rc = cmJsonInsertPairInt( h, objNodePtr, fieldPtr->fieldLabel, ival );
              break;

            case kRealTId:
              rc = cmJsonInsertPairReal( h, objNodePtr, fieldPtr->fieldLabel, rval );
              break;

            case kTrueTId:
            case kFalseTId:
              rc = cmJsonInsertPairBool( h, objNodePtr, fieldPtr->fieldLabel, ival );
              break;

            case kStringTId:
              rc = cmJsonInsertPairStringN( h, objNodePtr, fieldPtr->fieldLabel, cmLexTokenText(lexH), cmLexTokenCharCount(lexH) );
              break;

            default:
              { 
                assert(0); 
                goto errLabel;
              }
          }

          if( rc != kOkJsRC )
            goto errLabel;
          

          fieldPtr = fieldPtr->linkPtr;
        }
        break;

    }
  }

 errLabel:
  if( cmLexFinal(&lexH) != kOkLexRC )
  {
    rc = _cmJsonError( p, kLexErrJsRC, "Lexer finalize failed on CSV parse of '%s'.",cmStringNullGuard(iFn));
    goto errLabel;
  }

  lp = fieldList;
  while( lp!=NULL )
  {
    field_t* pp = lp->linkPtr;
    cmMemPtrFree(&lp->fieldLabel);
    cmMemPtrFree(&lp);
    lp = pp;
  }

  if( rc != kOkJsRC )
    _cmJsonRemoveNode( p, arrayNodePtr, true, true );

  if( rc == kOkJsRC && arrayNodePtrPtr != NULL )
    *arrayNodePtrPtr = arrayNodePtr;

  return rc;
}

cmJsRC_t      cmJsonToCSV(   cmJsonH_t h, const char* oFn, const cmJsonNode_t* arrayNodePtr )
{
  assert( arrayNodePtr->typeId == kArrayTId );

  typedef struct r_str
  {
    const char*   fieldLabel;
    unsigned      typeId;
    struct r_str* linkPtr;
  } field_t;

  cmJs_t*  p         = _cmJsonHandleToPtr(h);
  cmJsRC_t rc        = kOkJsRC;
  unsigned arrayCnt  = cmJsonChildCount(arrayNodePtr);
  field_t* fieldList = NULL;
  FILE*    fp        = NULL;
  field_t* lp        = NULL;
  unsigned i,j;

  // for each object in the array
  for(i=0; i<arrayCnt; ++i)
  {    
    const cmJsonNode_t* objNodePtr = cmJsonArrayElementC( arrayNodePtr, i );

    assert( objNodePtr->typeId == kObjectTId );

    // for each pair in the object
    for(j=0; j<cmJsonChildCount(objNodePtr); ++j)
    {
      const cmJsonNode_t* pairNodePtr = cmJsonArrayElementC( objNodePtr, j );

      const char*         pairLabel   = cmJsonPairLabel(pairNodePtr);

      lp = fieldList;

      // find this field in the field list
      for(; lp != NULL; lp = lp->linkPtr )
        if( strcmp(lp->fieldLabel,pairLabel) == 0)
        {
          unsigned typeId = cmJsonPairTypeId(pairNodePtr);

          switch( typeId )
          {
            case kIntTId:
            case kRealTId:
            case kTrueTId:
            case kFalseTId:
            case kStringTId:
              break;
            default:
              rc = _cmJsonError( p, kInvalidNodeTypeJsRC, "Field '%s' has type '%s' which cannot be written by cmJsonToCSV().",cmStringNullGuard(pairLabel),_cmJsonNodeTypeIdToLabel(typeId) );
              goto errLabel;
              
          }

          if( typeId != lp->typeId )
          {
            rc = _cmJsonError( p, kInvalidNodeTypeJsRC, "All nodes for a field label '%s' do not have the same type. '%s' != '%s'",cmStringNullGuard(pairLabel), _cmJsonNodeTypeIdToLabel(typeId), _cmJsonNodeTypeIdToLabel(lp->typeId) );
            goto errLabel;
          }

          break;
        }

      // if this field was not found then insert it
      if( lp == NULL )
      {
        field_t* rp = (field_t*)cmLHeapAlloc(p->heapH,sizeof(field_t));
        rp->fieldLabel = pairLabel;
        rp->linkPtr    = fieldList;
        rp->typeId     = cmJsonPairTypeId(pairNodePtr);
        fieldList      = rp;
      }
    }
  }

  // create the output file
  if((fp = fopen(oFn,"wt")) == NULL )
  {
    rc = _cmJsonError( p, kFileCreateErrJsRC, "CSV file '%s' create failed.", oFn );
    goto errLabel;
  }

  // write the field type
  lp = fieldList;
  for(; lp!=NULL; lp=lp->linkPtr)
  {
    fprintf(fp,"%s", _cmJsonNodeTypeIdToLabel(lp->typeId));

    if( lp->linkPtr != NULL )
      fprintf(fp,",");
  }
  fprintf(fp,"\n");
  
  // write the field label
  lp = fieldList;
  for(; lp!=NULL; lp=lp->linkPtr)
  {
    fprintf(fp,"\"%s\"", lp->fieldLabel);

    if( lp->linkPtr != NULL )
      fprintf(fp,",");
  }
  fprintf(fp,"\n");
    
  
  // for each object in the array
  for(i=0; i<arrayCnt; ++i)
  {
    unsigned            j;
    const cmJsonNode_t* objNodePtr = cmJsonArrayElementC( arrayNodePtr, i );
    unsigned            fieldCnt   = cmJsonChildCount(objNodePtr);

    lp = fieldList;

    // for each field ...
    for(j=0; lp!=NULL; lp=lp->linkPtr,++j)
    {
      cmJsonNode_t* valNodePtr;

      // ... locate the pair given the field label
      if((valNodePtr = cmJsonFindValue(h,lp->fieldLabel, objNodePtr, lp->typeId )) == NULL)
      {
        // no pair was found for the field label - output a NULL value
        switch( lp->typeId )
        {
          case kIntTId:
            fprintf(fp,"%i",0);
            break;

          case kRealTId:
            fprintf(fp,"%f",0.0);
            break;

          case kTrueTId:
          case kFalseTId:
            fprintf(fp,"%i",0);
            break;

          case kStringTId:
            fprintf(fp,"\"\"");
            break;

          default:
            assert(0);
            break;
        }

        //rc = _cmJsonError( p, kNodeNotFoundJsRC,"No field with label '%s' was found.", lp->fieldLabel);
        //goto errLabel;
      }
      else
      {
        switch( valNodePtr->typeId )
        {
          case kIntTId:
            fprintf(fp,"%i", valNodePtr->u.intVal);
            break;

          case kRealTId:
            fprintf(fp,"%e", valNodePtr->u.realVal);
            break;

          case kTrueTId:
          case kFalseTId:
            fprintf(fp,"%i", valNodePtr->u.boolVal);
            break;

          case kStringTId:
            fprintf(fp,"\"%s\"", valNodePtr->u.stringVal);
            break;

          default:
            assert(0);
            break;
        }
      }
      if( j < fieldCnt-1 )
        fprintf(fp,",");
      
    }
    
    fprintf(fp,"\n");
    
  }
  
 errLabel:

  if( fp != NULL )
    fclose(fp);

  lp = fieldList;
  while( lp!=NULL )
  {
    field_t* pp = lp->linkPtr;
    cmLHeapFree(p->heapH,lp);
    lp = pp;
  }

  return rc;
}


void  _cmJsonPrintIndent( cmRpt_t* rpt, unsigned indent )
{
  if( indent )
  {
    char spaces[indent+1];
    spaces[indent]=0;
    memset(spaces,' ',indent);
    cmRptPrint(rpt,spaces);
  }
}


void  _cmJsonPrintNode( const cmJsonNode_t* np, cmRpt_t* rpt, unsigned indent )
{
  unsigned    childCnt    = 0;
  char eoObjStr[]         = "}";
  char eoArrStr[]         = "]";
  char eoPairStr[]        = "\n";
  char commaStr[]         = ",\n";
  char colonStr[]         = ": ";
  const char* eleStr      = NULL;
  const char* lastEleStr  = NULL;
  const char* eoStr       = NULL;
  unsigned    localIndent = 0;


  switch(np->typeId)
  {
    case kObjectTId:
      cmRptPrint(rpt,"\n");
      _cmJsonPrintIndent(rpt,indent);      
      cmRptPrint(rpt,"{\n");
      childCnt    = cmJsonChildCount(np);
      eoStr       = eoObjStr;
      localIndent = 2;
      break;

    case kArrayTId:
      cmRptPrint(rpt,"\n");
      _cmJsonPrintIndent(rpt,indent);      
      cmRptPrint(rpt,"[\n");
      childCnt    = cmJsonChildCount(np);
      eoStr       = eoArrStr;
      localIndent = 2;
      eleStr      = commaStr;
      lastEleStr  = "\n";
      break;

    case kPairTId:
      childCnt = cmJsonChildCount(np);
      eleStr = colonStr;
      eoStr  = eoPairStr;
      break;

    case kStringTId:
      {
        const char* fmt0 = "\"%s\" ";
        const char* fmt1 = "%s";
        const char* fmt  = fmt0;

        // if this string is the label part of a pair
        if( np->u.stringVal != NULL && np->ownerPtr != NULL && cmJsonIsPair(np->ownerPtr) && np->ownerPtr->u.childPtr == np )
        {
          // and the label has no white space 
          char* cp = np->u.stringVal;
          while( *cp!=0 && isspace(*cp)==false )
            ++cp;
          
          // then print without quotes
          if( *cp == 0 )
            fmt = fmt1;
        }
        cmRptPrintf(rpt,fmt,np->u.stringVal==NULL ? "" : np->u.stringVal);
      }
      break;

    case kIntTId:
      cmRptPrintf(rpt,"%i ",np->u.intVal);
      break;

    case kRealTId:
      cmRptPrintf(rpt,"%f ",np->u.realVal);
      break;

    case kNullTId:
      cmRptPrint(rpt,"null ");
      break;

    case kTrueTId:
      cmRptPrint(rpt,"true ");
      break;

    case kFalseTId:
      cmRptPrint(rpt,"false ");
      break;
  }

  if( childCnt )
  {
    indent += localIndent;

    unsigned      i;
    cmJsonNode_t* cnp = np->u.childPtr;

    for(i=0; i<childCnt; ++i)
    {
      assert(cnp != NULL);

      if( np->typeId != kPairTId )
        _cmJsonPrintIndent(rpt,indent);

      _cmJsonPrintNode(cnp,rpt,indent);

      cnp = cnp->siblingPtr;

      if( i < childCnt-1 && eleStr != NULL )
        cmRptPrint(rpt,eleStr);

      if( i == childCnt-1 && lastEleStr != NULL )
        cmRptPrint(rpt,lastEleStr);
    }

    indent -= localIndent;
  }

  if( eoStr != NULL )
  {
    _cmJsonPrintIndent(rpt,indent);
    cmRptPrint(rpt,eoStr);
  }

}


void      cmJsonPrintTree( const cmJsonNode_t* np, cmRpt_t* rpt )
{
  _cmJsonPrintNode(np,rpt,0);
}

void _cmJsPrintFile(void* cmRptUserPtr, const cmChar_t* text)
{
  cmFileH_t* hp = (cmFileH_t*)cmRptUserPtr;
  cmFilePrint(*hp,text);
}

cmJsRC_t      cmJsonWrite( cmJsonH_t h, const cmJsonNode_t* np, const cmChar_t* fn )
{
  cmRpt_t   rpt;  
  cmFileH_t fh = cmFileNullHandle;
  cmJs_t*   p = _cmJsonHandleToPtr(h);

  if( np == NULL )
    np = cmJsonRoot(h);

  // create the output file
  if( cmFileOpen(&fh,fn,kWriteFileFl,p->err.rpt) != kOkFileRC )
    return _cmJsonError( p, kFileCreateErrJsRC, "Output file '%s' create failed.", fn );
  
  // setup a reporter to write to the file
  cmRptSetup(&rpt,_cmJsPrintFile,_cmJsPrintFile,&fh);

  // print the tree to the file
  cmJsonPrintTree(np,&rpt);

  // close the file
  if( cmFileClose(&fh) != kOkFileRC )
    return _cmJsonError( p, kFileCloseErrJsRC, "Output file  '%s' close failed.", fn );

  return kOkJsRC;
}
  
cmJsRC_t      cmJsonReport( cmJsonH_t h )
{
  cmJsRC_t rc;
  cmJs_t* p = _cmJsonHandleToPtr(h);

  if((rc = cmJsonValidateTree(h)) != kOkJsRC )
    return rc;

  if(p->rootPtr != NULL )
    _cmJsonPrintNode(p->rootPtr,p->err.rpt,0);

  return rc;
}

cmJsRC_t cmJsonErrorCode( cmJsonH_t h )
{
  cmJs_t* p = _cmJsonHandleToPtr(h);
  return p->rc;
}

 void    cmJsonClearErrorCode( cmJsonH_t h )
 {
   cmJs_t* p = _cmJsonHandleToPtr(h);
   p->rc = kOkJsRC;
 }

void _cmJsonTestVPrint( void* rptDataPtr, const char* fmt, va_list vl )
{
  vfprintf(stdout,fmt,vl);
}

void _cmJsonTestPrint( void* userPtr, const cmChar_t* text )
{
  fputs(text,stdout);
}


//{ { label:cmJsonEx }
//(
// cmJsonTest() demonstrates some JSON tree operations.
//)
//[
cmJsRC_t cmJsonTest( const char* fn, cmCtx_t* ctx )
{
  cmJsRC_t      rc  = kOkJsRC;
  cmJsRC_t      rc1 = kOkJsRC;
  cmJsonH_t     h   = cmJsonNullHandle;
  cmJsonH_t     h1  = cmJsonNullHandle;
  void*         sbp = NULL;
  unsigned      sbn = 0;
  cmJsonNode_t* np  = NULL;
  cmRpt_t*      rpt = &ctx->rpt;

  // initialize an empty JSON tree
  if((rc = cmJsonInitialize(&h,ctx)) != kOkJsRC )
    goto errLabel;

  // load the tree from a file
  if((rc = cmJsonParseFile(h,fn,NULL)) != kOkJsRC )
    goto errLabel;

  // print the tree
  cmJsonReport(h);

  // find an array member named 'mem14'
  if((np = cmJsonFindValue(h,"mem14",NULL,kArrayTId)) == NULL )
    cmRptPrint(rpt,"'mem14' not found.\n");
  else
  {
    cmRptPrint(rpt,"'mem14' found.\n");
    cmJsonPrintTree(np,rpt);
  }

  // remove the array node from the tree
  cmJsonRemoveNode(h,np, true);
  cmRptPrint(rpt,"mem14 removed.\n");

  // print the tree with the array node removed
  cmJsonPrintTree( cmJsonRoot(h), rpt );

  // serialize the tree into a dynamically allocated
  // buffer sbp[sbn].
  if((rc = cmJsonSerializeTree(h,NULL,&sbp,&sbn)) != kOkJsRC )
    goto errLabel;
  else
    cmRptPrint(rpt,"***Serialize Ok.****\n");

  // initialize an empty JSON tree
  if((rc = cmJsonInitialize(&h1,ctx)) != kOkJsRC )
    goto errLabel;

  // deserialize sbp[sbn] into the empty tree
  if((rc = cmJsonDeserialize(h1,sbp,NULL)) != kOkJsRC )
    goto errLabel;
  else
  {
    cmJsonPrintTree( cmJsonRoot(h1),rpt);
    cmRptPrint(rpt,"***Deserialize Ok.****\n");
  }
  
  // find an member node named 'mem5'
  if((np = cmJsonFindValue(h,"mem5",NULL,0)) == NULL )
    cmRptPrint(rpt,"mem5 not found.");

  // merge two sub-trees
  if( cmJsonMergeObjectNodes( h, np->u.childPtr, 
      np->u.childPtr->siblingPtr) != kOkJsRC )
  {
    cmRptPrint(rpt,"merge failed.");
  }
  else
  {
    cmJsonReport(h);
  }

 errLabel:

  // release the JSON trees
  rc = cmJsonFinalize(&h);
  rc1 = cmJsonFinalize(&h1);

  return rc == kOkJsRC ? rc1 : rc;
}
//]
//}
