/*************** json CPP Declares Source Code File (.H) ***************/
/*  Name: json.cpp   Version 1.6                                       */
/*                                                                     */
/*  (C) Copyright to the author Olivier BERTRAND          2014 - 2021  */
/*                                                                     */
/*  This file contains the JSON classes functions.                     */
/***********************************************************************/

/***********************************************************************/
/*  Include relevant sections of the MariaDB header file.              */
/***********************************************************************/
#include <my_global.h>
#include <m_string.h>

/***********************************************************************/
/*  Include application header files:                                  */
/*  global.h    is header containing all global declarations.          */
/*  plgdbsem.h  is header containing the DB application declarations.  */
/*  xjson.h     is header containing the JSON classes declarations.    */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
#include "json.h"

#define ARGS       MY_MIN(24,(int)len-i),s+MY_MAX(i-3,0)

#if defined(_WIN32)
#define EL  "\r\n"
#else
#define EL  "\n"
#undef     SE_CATCH                  // Does not work for Linux
#endif

#if defined(SE_CATCH)
/**************************************************************************/
/*  This is the support of catching C interrupts to prevent crashes.      */
/**************************************************************************/
#include <eh.h>

class SE_Exception {
public:
  SE_Exception(unsigned int n, PEXCEPTION_RECORD p) : nSE(n), eRec(p) {}
  ~SE_Exception() {}

  unsigned int      nSE;
  PEXCEPTION_RECORD eRec;
};  // end of class SE_Exception

void trans_func(unsigned int u, _EXCEPTION_POINTERS* pExp)
{
  throw SE_Exception(u, pExp->ExceptionRecord);
} // end of trans_func

char *GetExceptionDesc(PGLOBAL g, unsigned int e);
#endif   // SE_CATCH

char *GetJsonNull(void);
int   GetDefaultPrec(void);
int   PrepareColist(char*);

/***********************************************************************/
/* IsNum: check whether this string is all digits.                     */
/***********************************************************************/
bool IsNum(PSZ s)
{
  char* p = s;

  if (*p == '-')
    p++;

  if (*p == ']')
    return false;
  else for (; *p; p++)
    if (*p == ']')
      break;
    else if (!isdigit(*p))
      return false;

  return true;
} // end of IsNum

/***********************************************************************/
/* IsArray: check whether this is a Mongo array path.                  */
/***********************************************************************/
bool IsArray(PSZ s)
{
  char* p = s;

  if (!p || !*p)
    return false;
  else for (; *p; p++)
    if (*p == '.')
      break;
    else if (!isdigit(*p))
      return false;

  return true;
} // end of IsArray

/***********************************************************************/
/* NextChr: return the first found '[' or Sep pointer.                 */
/***********************************************************************/
char* NextChr(PSZ s, char sep)
{
  char* p1 = strchr(s, '[');
  char* p2 = strchr(s, sep);

  if (!p2)
    return p1;
  else if (p1)
    return MY_MIN(p1, p2);

  return p2;
} // end of NextChr

/***********************************************************************/
/* Stringified: check that this column is in the stringified list.     */
/***********************************************************************/
bool Stringified(PCSZ strfy, char *colname)
{
  if (strfy) {
    char *p, colist[512];
    int   n;

    strncpy(colist, strfy, sizeof(colist) - 1);
    n = PrepareColist(colist); 

    for (p = colist; n && p; p += (strlen(p) + 1), n--)
      if (!stricmp(p, colname))
        return true;

  } // endif strfy

  return false;
} // end of Stringified

#if 0
/***********************************************************************/
/* Allocate a VAL structure, make sure common field and Nd are zeroed. */
/***********************************************************************/
PVL AllocVal(PGLOBAL g, JTYP type)
{
  PVL vlp = (PVL)PlugSubAlloc(g, NULL, sizeof(VAL));

  vlp->LLn = 0;
  vlp->Nd = 0;
  vlp->Type = type;
  return vlp;
} // end of AllocVal
#endif // 0

/***********************************************************************/
/* Parse a json string.                                                */
/* Note: when pretty is not known, the caller set pretty to 3.         */
/***********************************************************************/
PJSON ParseJson(PGLOBAL g, char* s, size_t len, int* ptyp, bool* comma)
{
  int   i, pretty = (ptyp) ? *ptyp : 3;
  bool  b = false, pty[3] = { true,true,true };
  PJSON jsp = NULL;
  PJDOC jdp = NULL;

  if (trace(1))
    htrc("ParseJson: s=%.10s len=%zd\n", s, len);

  if (!s || !len) {
    strcpy(g->Message, "Void JSON object");
    return NULL;
  } else if (comma)
    *comma = false;

  // Trying to guess the pretty format
  if (s[0] == '[' && (s[1] == '\n' || (s[1] == '\r' && s[2] == '\n')))
    pty[0] = false;

  try {
    jdp = new(g) JDOC;
    jdp->s = s;
    jdp->len = len;
    jdp->pty = pty;

    for (i = 0; i < jdp->len; i++)
      switch (s[i]) {
      case '[':
        if (jsp)
          jsp = jdp->ParseAsArray(g, i, pretty, ptyp);
        else
          jsp = jdp->ParseArray(g, ++i);

        break;
      case '{':
        if (jsp)
          jsp = jdp->ParseAsArray(g, i, pretty, ptyp);
        else if (!(jsp = jdp->ParseObject(g, ++i)))
          throw 2;

        break;
      case ' ':
      case '\t':
      case '\n':
      case '\r':
        break;
      case ',':
        if (jsp && (pretty == 1 || pretty == 3)) {
          if (comma)
            *comma = true;

          pty[0] = pty[2] = false;
          break;
        } // endif pretty

        snprintf(g->Message, sizeof(g->Message), "Unexpected ',' (pretty=%d)", pretty);
        throw 3;
      case '(':
        b = true;
        break;
      case ')':
        if (b) {
          b = false;
          break;
        } // endif b
        /* falls through */
      default:
        if (jsp)
          jsp = jdp->ParseAsArray(g, i, pretty, ptyp);
        else if (!(jsp = jdp->ParseValue(g, i)))
          throw 4;

        break;
      }; // endswitch s[i]

    if (!jsp)
      snprintf(g->Message, sizeof(g->Message), "Invalid Json string '%.*s'", MY_MIN((int)len, 50), s);
    else if (ptyp && pretty == 3) {
      *ptyp = 3;     // Not recognized pretty

      for (i = 0; i < 3; i++)
        if (pty[i]) {
          *ptyp = i;
          break;
        } // endif pty

    } // endif ptyp

  } catch (int n) {
    if (trace(1))
      htrc("Exception %d: %s\n", n, g->Message);
    jsp = NULL;
  } catch (const char* msg) {
    strcpy(g->Message, msg);
    jsp = NULL;
  } // end catch

  return jsp;
} // end of ParseJson

/***********************************************************************/
/* Serialize a JSON document tree:                                     */
/***********************************************************************/
PSZ Serialize(PGLOBAL g, PJSON jsp, char* fn, int pretty) {
  PSZ   str = NULL;
  bool  b = false, err = true;
  JOUT* jp;
  FILE* fs = NULL;
  PJDOC jdp = NULL;

  g->Message[0] = 0;

  try {
    jdp = new(g) JDOC; // MUST BE ALLOCATED BEFORE jp !!!!!
    jdp->dfp = GetDefaultPrec();

    if (!jsp) {
      safe_strcpy(g->Message, sizeof(g->Message), "Null json tree");
      throw 1;
    } else if (!fn) {
      // Serialize to a string
      jp = new(g) JOUTSTR(g);
      b = pretty == 1;
    } else {
      if (!(fs = fopen(fn, "wb"))) {
        snprintf(g->Message, sizeof(g->Message), MSG(OPEN_MODE_ERROR) ": %s",
                 "w", (int)errno, fn, strerror(errno));
        throw 2;
      } else if (pretty >= 2) {
        // Serialize to a pretty file
        jp = new(g)JOUTPRT(g, fs);
      } else {
        // Serialize to a flat file
        b = true;
        jp = new(g)JOUTFILE(g, fs, pretty);
      } // endif's

    } // endif's

    jdp->SetJp(jp);

    switch (jsp->GetType()) {
    case TYPE_JAR:
      err = jdp->SerializeArray((PJAR)jsp, b);
      break;
    case TYPE_JOB:
      err = ((b && jp->Prty()) && jp->WriteChr('\t'));
      err |= jdp->SerializeObject((PJOB)jsp);
      break;
    case TYPE_JVAL:
      err = jdp->SerializeValue((PJVAL)jsp);
      break;
    default:
      strcpy(g->Message, "Invalid json tree");
    } // endswitch Type

    if (fs) {
      fputs(EL, fs);
      fclose(fs);
      str = (err) ? NULL : strcpy(g->Message, "Ok");
    } else if (!err) {
      str = ((JOUTSTR*)jp)->Strp;
      jp->WriteChr('\0');
      PlugSubAlloc(g, NULL, ((JOUTSTR*)jp)->N);
    } else {
      if (!g->Message[0])
        strcpy(g->Message, "Error in Serialize");

    } // endif's

  } catch (int n) {
    if (trace(1))
      htrc("Exception %d: %s\n", n, g->Message);
    str = NULL;
  } catch (const char* msg) {
    strcpy(g->Message, msg);
    str = NULL;
  } // end catch

  return str;
} // end of Serialize


/* -------------------------- Class JOUTSTR -------------------------- */

/***********************************************************************/
/* JOUTSTR constructor.                                                */
/***********************************************************************/
JOUTSTR::JOUTSTR(PGLOBAL g) : JOUT(g) {
  PPOOLHEADER pph = (PPOOLHEADER)g->Sarea;

  N = 0;
  Max = pph->FreeBlk;
  Max = (Max > 32) ? Max - 32 : Max;
  Strp = (char*)PlugSubAlloc(g, NULL, 0);  // Size not know yet
} // end of JOUTSTR constructor

/***********************************************************************/
/* Concatenate a string to the Serialize string.                       */
/***********************************************************************/
bool JOUTSTR::WriteStr(const char* s) {
  if (s) {
    size_t len = strlen(s);

    if (N + len > Max)
      return true;

    memcpy(Strp + N, s, len);
    N += len;
    return false;
  } else
    return true;

} // end of WriteStr

/***********************************************************************/
/* Concatenate a character to the Serialize string.                    */
/***********************************************************************/
bool JOUTSTR::WriteChr(const char c) {
  if (N + 1 > Max)
    return true;

  Strp[N++] = c;
  return false;
} // end of WriteChr

/***********************************************************************/
/* Escape and Concatenate a string to the Serialize string.            */
/***********************************************************************/
bool JOUTSTR::Escape(const char* s)
{
  if (s) {
    WriteChr('"');

    for (unsigned int i = 0; s[i]; i++)
      switch (s[i]) {
        case '"':
        case '\\':
        case '\t':
        case '\n':
        case '\r':
        case '\b':
        case '\f': WriteChr('\\');
          // fall through
        default:
          WriteChr(s[i]);
          break;
      } // endswitch s[i]

    WriteChr('"');
  } else
    WriteStr("null");

  return false;
} // end of Escape

/* ------------------------- Class JOUTFILE -------------------------- */

/***********************************************************************/
/* Write a string to the Serialize file.                               */
/***********************************************************************/
bool JOUTFILE::WriteStr(const char* s)
{
  // This is temporary
  fputs(s, Stream);
  return false;
} // end of WriteStr

/***********************************************************************/
/* Write a character to the Serialize file.                            */
/***********************************************************************/
bool JOUTFILE::WriteChr(const char c)
{
  // This is temporary
  fputc(c, Stream);
  return false;
} // end of WriteChr

/***********************************************************************/
/* Escape and Concatenate a string to the Serialize string.            */
/***********************************************************************/
bool JOUTFILE::Escape(const char* s)
{
  // This is temporary
  if (s) {
    fputc('"', Stream);

    for (unsigned int i = 0; s[i]; i++)
      switch (s[i]) {
        case '"':  fputs("\\\"", Stream); break;
        case '\\': fputs("\\\\", Stream); break;
        case '\t': fputs("\\t", Stream); break;
        case '\n': fputs("\\n", Stream); break;
        case '\r': fputs("\\r", Stream); break;
        case '\b': fputs("\\b", Stream); break;
        case '\f': fputs("\\f", Stream); break;
        default:
          fputc(s[i], Stream);
          break;
      } // endswitch s[i]

    fputc('"', Stream);
  } else
    fputs("null", Stream);

  return false;
} // end of Escape

/* ------------------------- Class JOUTPRT --------------------------- */

/***********************************************************************/
/* Write a string to the Serialize pretty file.                        */
/***********************************************************************/
bool JOUTPRT::WriteStr(const char* s)
{
  // This is temporary
  if (B) {
    fputs(EL, Stream);
    M--;

    for (int i = 0; i < M; i++)
      fputc('\t', Stream);

    B = false;
  } // endif B

  fputs(s, Stream);
  return false;
} // end of WriteStr

/***********************************************************************/
/* Write a character to the Serialize pretty file.                     */
/***********************************************************************/
bool JOUTPRT::WriteChr(const char c)
{
  switch (c) {
  case ':':
    fputs(": ", Stream);
    break;
  case '{':
  case '[':
#if 0
    if (M)
      fputs(EL, Stream);

    for (int i = 0; i < M; i++)
      fputc('\t', Stream);
#endif // 0

    fputc(c, Stream);
    fputs(EL, Stream);
    M++;

    for (int i = 0; i < M; i++)
      fputc('\t', Stream);

    break;
  case '}':
  case ']':
    M--;
    fputs(EL, Stream);

    for (int i = 0; i < M; i++)
      fputc('\t', Stream);

    fputc(c, Stream);
    B = true;
    break;
  case ',':
    fputc(c, Stream);
    fputs(EL, Stream);

    for (int i = 0; i < M; i++)
      fputc('\t', Stream);

    B = false;
    break;
  default:
    fputc(c, Stream);
  } // endswitch c

  return false;
} // end of WriteChr

/* --------------------------- Class JDOC ---------------------------- */

/***********************************************************************/
/* Parse several items as being in an array.                           */
/***********************************************************************/
PJAR JDOC::ParseAsArray(PGLOBAL g, int& i, int pretty, int *ptyp)
{
  if (pty[0] && (!pretty || pretty > 2)) {
    PJAR jsp;

    if ((jsp = ParseArray(g, (i = 0))) && ptyp && pretty == 3)
      *ptyp = (pty[0]) ? 0 : 3;

    return jsp;
  } else
    strcpy(g->Message, "More than one item in file");

  return NULL;
} // end of ParseAsArray

/***********************************************************************/
/* Parse a JSON Array.                                                 */
/***********************************************************************/
PJAR JDOC::ParseArray(PGLOBAL g, int& i)
{
  int  level = 0;
  bool b = (!i);
  PJAR jarp = new(g) JARRAY;

  for (; i < len; i++)
    switch (s[i]) {
      case ',':
        if (level < 2) {
          snprintf(g->Message, sizeof(g->Message), "Unexpected ',' near %.*s",ARGS);
          throw 1;
        } else
          level = 1;

        break;
      case ']':
        if (level == 1) {
          snprintf(g->Message, sizeof(g->Message), "Unexpected ',]' near %.*s", ARGS);
          throw 1;
        } // endif level

        jarp->InitArray(g);
        return jarp;
      case '\n':
        if (!b)
          pty[0] = pty[1] = false;
      case '\r':
      case ' ':
      case '\t':
        break;
      default:
        if (level == 2) {
          snprintf(g->Message, sizeof(g->Message), "Unexpected value near %.*s", ARGS);
          throw 1;
        } else
          jarp->AddArrayValue(g, ParseValue(g, i));

        level = (b) ? 1 : 2;
        break;
    }; // endswitch s[i]

  if (b) {
    // Case of Pretty == 0
    jarp->InitArray(g);
    return jarp;
  } // endif b

  throw ("Unexpected EOF in array");
} // end of ParseArray

/***********************************************************************/
/* Parse a JSON Object.                                                */
/***********************************************************************/
PJOB JDOC::ParseObject(PGLOBAL g, int& i)
{
  PSZ   key;
  int   level = -1;
  PJOB  jobp = new(g) JOBJECT;
  PJPR  jpp = NULL;

  for (; i < len; i++)
    switch (s[i]) {
      case '"':
        if (level < 2) {
          key = ParseString(g, ++i);
          jpp = jobp->AddPair(g, key);
          level = 1;
        } else {
          snprintf(g->Message, sizeof(g->Message), "misplaced string near %.*s", ARGS);
          throw 2;
        } // endif level

        break;
      case ':':
        if (level == 1) {
          jpp->Val = ParseValue(g, ++i);
          level = 2;
        } else {
          snprintf(g->Message, sizeof(g->Message), "Unexpected ':' near %.*s", ARGS);
          throw 2;
        } // endif level

        break;
      case ',':
        if (level < 2) {
          snprintf(g->Message, sizeof(g->Message), "Unexpected ',' near %.*s", ARGS);
          throw 2;
        } else
          level = 0;

        break;
      case '}':
        if (level == 0 || level == 1) {
          snprintf(g->Message, sizeof(g->Message), "Unexpected '}' near %.*s", ARGS);
          throw 2;
          } // endif level

        return jobp;
      case '\n':
        pty[0] = pty[1] = false;
      case '\r':
      case ' ':
      case '\t':
        break;
      default:
        snprintf(g->Message, sizeof(g->Message), "Unexpected character '%c' near %.*s",
                s[i], ARGS);
        throw 2;
    }; // endswitch s[i]

  strcpy(g->Message, "Unexpected EOF in Object");
  throw 2;
} // end of ParseObject

/***********************************************************************/
/* Parse a JSON Value.                                                 */
/***********************************************************************/
PJVAL JDOC::ParseValue(PGLOBAL g, int& i)
{
  PJVAL jvp = new(g) JVALUE;

  for (; i < len; i++)
    switch (s[i]) {
      case '\n':
        pty[0] = pty[1] = false;
      case '\r':
      case ' ':
      case '\t':
        break;
      default:
        goto suite;
    } // endswitch

 suite:
  switch (s[i]) {
    case '[':
      jvp->Jsp = ParseArray(g, ++i);
      jvp->DataType = TYPE_JSON;
      break;
    case '{':
      jvp->Jsp = ParseObject(g, ++i);
      jvp->DataType = TYPE_JSON;
      break;
    case '"':
//    jvp->Val = AllocVal(g, TYPE_STRG);
      jvp->Strp = ParseString(g, ++i);
      jvp->DataType = TYPE_STRG;
      break;
    case 't':
      if (!strncmp(s + i, "true", 4)) {
//      jvp->Val = AllocVal(g, TYPE_BOOL);
        jvp->B = true;
        jvp->DataType = TYPE_BOOL;
        i += 3;
      } else
        goto err;

      break;
    case 'f':
      if (!strncmp(s + i, "false", 5)) {
//      jvp->Val = AllocVal(g, TYPE_BOOL);
        jvp->B = false;
        jvp->DataType = TYPE_BOOL;
        i += 4;
      } else
        goto err;

      break;
    case 'n':
      if (!strncmp(s + i, "null", 4)) {
        jvp->DataType = TYPE_NULL;
        i += 3;
      } else
        goto err;

      break;
    case '-':
    default:
      if (s[i] == '-' || isdigit(s[i]))
        ParseNumeric(g, i, jvp);
      else
        goto err;

    }; // endswitch s[i]

  return jvp;

err:
  snprintf(g->Message, sizeof(g->Message), "Unexpected character '%c' near %.*s", s[i], ARGS);
  throw 3;
} // end of ParseValue

/***********************************************************************/
/*  Unescape and parse a JSON string.                                  */
/***********************************************************************/
char *JDOC::ParseString(PGLOBAL g, int& i)
{
  uchar *p;
  int    n = 0;

  // Be sure of memory availability
  if (((size_t)len + 1 - i) > ((PPOOLHEADER)g->Sarea)->FreeBlk)
    throw("ParseString: Out of memory");

  // The size to allocate is not known yet
  p = (uchar*)PlugSubAlloc(g, NULL, 0);

  for (; i < len; i++)
    switch (s[i]) {
      case '"':
        p[n++] = 0;
        PlugSubAlloc(g, NULL, n);
        return (char*)p;
      case '\\':
        if (++i < len) {
          if (s[i] == 'u') {
            if (len - i > 5) {
//            if (charset == utf8) {
                char xs[5];
                uint hex;

                xs[0] = s[++i];
                xs[1] = s[++i];
                xs[2] = s[++i];
                xs[3] = s[++i];
                xs[4] = 0;
                hex = strtoul(xs, NULL, 16);

                if (hex < 0x80) {
                  p[n] = (uchar)hex;
                } else if (hex < 0x800) {
                  p[n++] = (uchar)(0xC0 | (hex >> 6));
                  p[n]   = (uchar)(0x80 | (hex & 0x3F));
                } else if (hex < 0x10000) {
                  p[n++] = (uchar)(0xE0 | (hex >> 12));
                  p[n++] = (uchar)(0x80 | ((hex >> 6) & 0x3f));
                  p[n]   = (uchar)(0x80 | (hex & 0x3f));
                } else
                  p[n] = '?';

#if 0
              } else {
                char xs[3];
                UINT hex;

                i += 2;
                xs[0] = s[++i];
                xs[1] = s[++i];
                xs[2] = 0;
                hex = strtoul(xs, NULL, 16);
                p[n] = (char)hex;
              } // endif charset
#endif // 0
            } else
              goto err;

          } else switch(s[i]) {
            case 't': p[n] = '\t'; break;
            case 'n': p[n] = '\n'; break;
            case 'r': p[n] = '\r'; break;
            case 'b': p[n] = '\b'; break;
            case 'f': p[n] = '\f'; break;
            default:  p[n] = s[i]; break;
            } // endswitch

          n++;
        } else
          goto err;

        break;
      default:
        p[n++] = s[i];
        break;
      }; // endswitch s[i]

 err:
  throw("Unexpected EOF in String");
} // end of ParseString

/***********************************************************************/
/* Parse a JSON numeric value.                                         */
/***********************************************************************/
void JDOC::ParseNumeric(PGLOBAL g, int& i, PJVAL vlp)
{
  char  buf[50];
  int   n = 0;
  short nd = 0;
  bool  has_dot = false;
  bool  has_e = false;
  bool  found_digit = false;
//PVL   vlp = NULL;

  for (; i < len; i++) {
    switch (s[i]) {
      case '.':
        if (!found_digit || has_dot || has_e)
          goto err;

        has_dot = true;
        break;
      case 'e':
      case 'E':
        if (!found_digit || has_e)
          goto err;

        has_e = true;
        found_digit = false;
        break;
      case '+':
        if (!has_e)
          goto err;

        // fall through
      case '-':
        if (found_digit)
          goto err;

        break;
      default:
        if (isdigit(s[i])) {
          if (has_dot && !has_e)
            nd++;       // Number of decimals

          found_digit = true;
        } else
          goto fin;

      }; // endswitch s[i]

    buf[n++] = s[i];
    } // endfor i

 fin:
  if (found_digit) {
    buf[n] = 0;

    if (has_dot || has_e) {
      double dv = strtod(buf, NULL);

//    vlp = AllocVal(g, TYPE_DBL);
      vlp->F = dv;
      vlp->Nd = nd;
      vlp->DataType = TYPE_DBL;
    } else {
      long long iv = strtoll(buf, NULL, 10);

      if (iv > INT_MAX32 || iv < INT_MIN32) {
//      vlp = AllocVal(g, TYPE_BINT);
        vlp->LLn = iv;
        vlp->DataType = TYPE_BINT;
      } else {
//      vlp = AllocVal(g, TYPE_INTG);
        vlp->N = (int)iv;
        vlp->DataType = TYPE_INTG;
      } // endif iv

    } // endif has

    i--;  // Unstack  following character
    return;
  } else
    throw("No digit found");

 err:
  throw("Unexpected EOF in number");
} // end of ParseNumeric

/***********************************************************************/
/* Serialize a JSON Array.                                             */
/***********************************************************************/
bool JDOC::SerializeArray(PJAR jarp, bool b)
{
  bool first = true;

  if (b) {
    if (js->Prty()) {
      if (js->WriteChr('['))
        return true;
      else if (js->Prty() == 1 && (js->WriteStr(EL) || js->WriteChr('\t')))
        return true;

    } // endif Prty

  } else if (js->WriteChr('['))
    return true;

  for (int i = 0; i < jarp->size(); i++) {
    if (first)
      first = false;
    else if ((!b || js->Prty()) && js->WriteChr(','))
      return true;
    else if (b) {
      if (js->Prty() < 2 && js->WriteStr(EL))
        return true;
      else if (js->Prty() == 1 && js->WriteChr('\t'))
        return true;

    } // endif b

    if (SerializeValue(jarp->GetArrayValue(i)))
      return true;

    } // endfor i

  if (b && js->Prty() == 1 && js->WriteStr(EL))
    return true;

  return ((!b || js->Prty()) && js->WriteChr(']'));
} // end of SerializeArray

/***********************************************************************/
/* Serialize a JSON Object.                                            */
/***********************************************************************/
bool JDOC::SerializeObject(PJOB jobp)
{
  bool first = true;

  if (js->WriteChr('{'))
    return true;

  for (PJPR pair = jobp->GetFirst(); pair; pair = pair->Next) {
    if (first)
      first = false;
    else if (js->WriteChr(','))
      return true;

    if (js->WriteChr('"') ||
        js->WriteStr(pair->Key) ||
        js->WriteChr('"') ||
        js->WriteChr(':') ||
        SerializeValue(pair->Val))
      return true;

    } // endfor i

  return js->WriteChr('}');
} // end of SerializeObject

/***********************************************************************/
/* Serialize a JSON Value.                                             */
/***********************************************************************/
bool JDOC::SerializeValue(PJVAL jvp)
{
  char buf[64];
  PJAR jap;
  PJOB jop;
  //PVL  vlp;

  if ((jap = jvp->GetArray()))
    return SerializeArray(jap, false);
  else if ((jop = jvp->GetObject()))
    return SerializeObject(jop);
//else if (!(vlp = jvp->Val))
//  return js->WriteStr("null");
  else switch (jvp->DataType) {
    case TYPE_BOOL:
      return js->WriteStr(jvp->B ? "true" : "false");
    case TYPE_STRG:
    case TYPE_DTM:
      return js->Escape(jvp->Strp);
    case TYPE_INTG:
      snprintf(buf, sizeof(buf), "%d", jvp->N);
      return js->WriteStr(buf);
    case TYPE_BINT:
      snprintf(buf, sizeof(buf), "%lld", jvp->LLn);
      return js->WriteStr(buf);
    case TYPE_DBL:  // dfp to limit to the default number of decimals
      snprintf(buf, sizeof(buf), "%.*f", MY_MIN(jvp->Nd, dfp), jvp->F);
      return js->WriteStr(buf);
    case TYPE_NULL:
      return js->WriteStr("null");
    default:
      return js->WriteStr("???");   // TODO
  } // endswitch Type

  strcpy(js->g->Message, "Unrecognized value");
  return true;
} // end of SerializeValue

/* -------------------------- Class JOBJECT -------------------------- */

/***********************************************************************/
/* Return the number of pairs in this object.                          */
/***********************************************************************/
int JOBJECT::GetSize(bool b) {
  int n = 0;

  for (PJPR jpp = First; jpp; jpp = jpp->Next)
    // If b return only non null pairs
    if (!b || jpp->Val && !jpp->Val->IsNull())
      n++;

  return n;
} // end of GetSize

/***********************************************************************/
/* Add a new pair to an Object.                                        */
/***********************************************************************/
PJPR JOBJECT::AddPair(PGLOBAL g, PCSZ key)
{
  PJPR jpp = (PJPR)PlugSubAlloc(g, NULL, sizeof(JPAIR));

  jpp->Key = key;
  jpp->Next = NULL;
  jpp->Val = NULL;

  if (Last)
    Last->Next = jpp;
  else
    First = jpp;

  Last = jpp;
  return jpp;
} // end of AddPair

/***********************************************************************/
/* Return all keys as an array.                                        */
/***********************************************************************/
PJAR JOBJECT::GetKeyList(PGLOBAL g)
{
  PJAR jarp = new(g) JARRAY();

  for (PJPR jpp = First; jpp; jpp = jpp->Next)
    jarp->AddArrayValue(g, new(g) JVALUE(g, jpp->Key));

  jarp->InitArray(g);
  return jarp;
} // end of GetKeyList

/***********************************************************************/
/* Return all values as an array.                                      */
/***********************************************************************/
PJAR JOBJECT::GetValList(PGLOBAL g)
{
  PJAR jarp = new(g) JARRAY();

  for (PJPR jpp = First; jpp; jpp = jpp->Next)
    jarp->AddArrayValue(g, jpp->Val);

  jarp->InitArray(g);
  return jarp;
} // end of GetValList

/***********************************************************************/
/* Get the value corresponding to the given key.                       */
/***********************************************************************/
PJVAL JOBJECT::GetKeyValue(const char* key)
{
  for (PJPR jp = First; jp; jp = jp->Next)
    if (!strcmp(jp->Key, key))
      return jp->Val;

  return NULL;
} // end of GetValue;

/***********************************************************************/
/* Return the text corresponding to all keys (XML like).               */
/***********************************************************************/
PSZ JOBJECT::GetText(PGLOBAL g, PSTRG text)
{
	if (First) {
		bool b;

		if (!text) {
			text = new(g) STRING(g, 256);
			b = true;
		} else {
			if (text->GetLastChar() != ' ')
				text->Append(' ');

			b = false;
		}	// endif text

		if (b && !First->Next && !strcmp(First->Key, "$date")) {
			int i;
			PSZ s;

			First->Val->GetText(g, text);
			s = text->GetStr();
			i = (s[1] == '-' ? 2 : 1);

			if (IsNum(s + i)) {
				// Date is in milliseconds
				int j = text->GetLength();

				if (j >= 4 + i) {
					s[j - 3] = 0;        // Change it to seconds
					text->SetLength((uint)strlen(s));
				} else
					text->Set(" 0");

			} // endif text

		} else for (PJPR jp = First; jp; jp = jp->Next) {
			jp->Val->GetText(g, text);

			if (jp->Next)
				text->Append(' ');

		}	// endfor jp

		if (b) {
			text->Trim();
			return text->GetStr();
		}	// endif b

	} // endif First

	return NULL;
} // end of GetText;

/***********************************************************************/
/* Merge two objects.                                                  */
/***********************************************************************/
bool JOBJECT::Merge(PGLOBAL g, PJSON jsp)
{
  if (jsp->GetType() != TYPE_JOB) {
    strcpy(g->Message, "Second argument is not an object");
    return true;
  } // endif Type

  PJOB jobp = (PJOB)jsp;

  for (PJPR jpp = jobp->First; jpp; jpp = jpp->Next)
    SetKeyValue(g, jpp->Val, jpp->Key);

  return false;
} // end of Marge;

/***********************************************************************/
/* Set or add a value corresponding to the given key.                  */
/***********************************************************************/
void JOBJECT::SetKeyValue(PGLOBAL g, PJVAL jvp, PCSZ key)
{
  PJPR jp;

  for (jp = First; jp; jp = jp->Next)
    if (!strcmp(jp->Key, key)) {
      jp->Val = jvp;
      break;
      } // endif key

  if (!jp) {
    jp = AddPair(g, key);
    jp->Val = jvp;
    } // endif jp

} // end of SetValue

/***********************************************************************/
/* Delete a value corresponding to the given key.                      */
/***********************************************************************/
void JOBJECT::DeleteKey(PCSZ key)
{
  PJPR jp, *pjp = &First;

  for (jp = First; jp; jp = jp->Next)
    if (!strcmp(jp->Key, key)) {
      *pjp = jp->Next;
      break;
    } else
      pjp = &jp->Next;

} // end of DeleteKey

/***********************************************************************/
/* True if void or if all members are nulls.                           */
/***********************************************************************/
bool JOBJECT::IsNull(void)
{
  for (PJPR jp = First; jp; jp = jp->Next)
    if (!jp->Val->IsNull())
      return false;

  return true;
} // end of IsNull

/* -------------------------- Class JARRAY --------------------------- */

/***********************************************************************/
/* JARRAY constructor.                                                 */
/***********************************************************************/
JARRAY::JARRAY(void) : JSON()
{
	Type = TYPE_JAR;  
	Size = 0; 
	Alloc = 0; 
	First = Last = NULL; 
	Mvals = NULL;
}	// end of JARRAY constructor

/***********************************************************************/
/* Return the number of values in this object.                         */
/***********************************************************************/
int JARRAY::GetSize(bool b)
{
  if (b) {
    // Return only non null values
    int n = 0;

    for (PJVAL jvp = First; jvp; jvp = jvp->Next)
      if (!jvp->IsNull())
        n++;

    return n;
  } else
    return Size;

} // end of GetSize

/***********************************************************************/
/* Make the array of values from the values list.                      */
/***********************************************************************/
void JARRAY::InitArray(PGLOBAL g)
{
  int   i;
  PJVAL jvp, *pjvp = &First;

  for (Size = 0, jvp = First; jvp; jvp = jvp->Next)
    if (!jvp->Del)
      Size++;

  if (Size > Alloc) {
    // No need to realloc after deleting values
    Mvals = (PJVAL*)PlugSubAlloc(g, NULL, Size * sizeof(PJVAL));
    Alloc = Size;
  } // endif Size

  for (i = 0, jvp = First; jvp; jvp = jvp->Next)
    if (!jvp->Del) {
      Mvals[i++] = jvp;
      pjvp = &jvp->Next;
      Last = jvp;
    } else
      *pjvp = jvp->Next;

} // end of InitArray

/***********************************************************************/
/* Get the Nth value of an Array.                                      */
/***********************************************************************/
PJVAL JARRAY::GetArrayValue(int i)
{
  if (Mvals && i >= 0 && i < Size)
    return Mvals[i];
  else if (Mvals && i < 0 && i >= -Size)
    return Mvals[Size + i];
  else
    return NULL;
} // end of GetValue

/***********************************************************************/
/* Add a Value to the Array Value list.                                */
/***********************************************************************/
PJVAL JARRAY::AddArrayValue(PGLOBAL g, PJVAL jvp, int *x)
{
  if (!jvp)
    jvp = new(g) JVALUE;

  if (x) {
    int   i = 0, n = *x;
    PJVAL jp, *jpp = &First;

    for (jp = First; jp && i < n; i++, jp = *(jpp = &jp->Next));

    (*jpp) = jvp;

    if (!(jvp->Next = jp))
      Last = jvp;

  } else {
    if (!First)
      First = jvp;
    else if (Last == First)
      First->Next = Last = jvp;
    else
      Last->Next = jvp;

    Last = jvp;
    Last->Next = NULL;
  } // endif x

  return jvp;
} // end of AddValue

/***********************************************************************/
/* Merge two arrays.                                                   */
/***********************************************************************/
bool JARRAY::Merge(PGLOBAL g, PJSON jsp)
{
  if (jsp->GetType() != TYPE_JAR) {
    strcpy(g->Message, "Second argument is not an array");
    return true;
  } // endif Type

  PJAR arp = (PJAR)jsp;

  for (int i = 0; i < arp->size(); i++)
    AddArrayValue(g, arp->GetArrayValue(i));

  InitArray(g);
  return false;
} // end of Merge

/***********************************************************************/
/* Set the nth Value of the Array Value list or add it.                */
/***********************************************************************/
void JARRAY::SetArrayValue(PGLOBAL g, PJVAL jvp, int n)
{
  int   i = 0;
  PJVAL jp, *jpp = &First;

  for (jp = First; i < n; i++, jp = *(jpp = &jp->Next))
    if (!jp)
      *jpp = jp = new(g) JVALUE;

  *jpp = jvp;
  jvp->Next = (jp ? jp->Next : NULL);
} // end of SetValue

/***********************************************************************/
/* Return the text corresponding to all values.                        */
/***********************************************************************/
PSZ JARRAY::GetText(PGLOBAL g, PSTRG text)
{
	if (First) {
		bool  b;
		PJVAL jp;

		if (!text) {
			text = new(g) STRING(g, 256);
			b = true;
		} else {
			if (text->GetLastChar() != ' ')
				text->Append(" (");
			else
				text->Append('(');

			b = false;
		}

		for (jp = First; jp; jp = jp->Next) {
			jp->GetText(g, text);

			if (jp->Next)
				text->Append(", ");
			else if (!b)
				text->Append(')');

		}	// endfor jp

		if (b) {
			text->Trim();
			return text->GetStr();
		}	// endif b

	} // endif First

	return NULL;
} // end of GetText;

/***********************************************************************/
/* Delete a Value from the Arrays Value list.                          */
/***********************************************************************/
bool JARRAY::DeleteValue(int n)
{
  PJVAL jvp = GetArrayValue(n);

  if (jvp) {
    jvp->Del = true;
    return false;
  } else
    return true;

} // end of DeleteValue

/***********************************************************************/
/* True if void or if all members are nulls.                           */
/***********************************************************************/
bool JARRAY::IsNull(void)
{
  for (int i = 0; i < Size; i++)
    if (!Mvals[i]->IsNull())
      return false;

  return true;
} // end of IsNull

/* -------------------------- Class JVALUE- -------------------------- */

/***********************************************************************/
/* Constructor for a JVALUE.                                           */
/***********************************************************************/
JVALUE::JVALUE(PJSON jsp) : JSON()
{
  if (jsp && jsp->GetType() == TYPE_JVAL) {
    PJVAL jvp = (PJVAL)jsp;

//  Val = ((PJVAL)jsp)->GetVal();
    if (jvp->DataType == TYPE_JSON) {
      Jsp = jvp->GetJsp();
      DataType = TYPE_JSON;
      Nd = 0;
    } else {
      LLn = jvp->LLn;   // Must be LLn on 32 bit machines
      Nd = jvp->Nd;
      DataType = jvp->DataType;
    } // endelse Jsp
 
  } else {
    Jsp = jsp;
//  Val = NULL;
    DataType = Jsp ? TYPE_JSON : TYPE_NULL;
    Nd = 0;
  } // endif Type

  Next = NULL;
  Del = false;
  Type = TYPE_JVAL;
} // end of JVALUE constructor

#if 0
/***********************************************************************/
/* Constructor for a JVALUE with a given string or numeric value.      */
/***********************************************************************/
JVALUE::JVALUE(PGLOBAL g, PVL vlp) : JSON()
{
  Jsp = NULL;
  Val = vlp;
  Next = NULL;
  Del = false;
  Type = TYPE_JVAL;
} // end of JVALUE constructor
#endif // 0

/***********************************************************************/
/* Constructor for a JVALUE with a given string or numeric value.      */
/***********************************************************************/
JVALUE::JVALUE(PGLOBAL g, PVAL valp) : JSON() {
  Jsp = NULL;
//Val = NULL;
  SetValue(g, valp);
  Next = NULL;
  Del = false;
  Type = TYPE_JVAL;
} // end of JVALUE constructor

/***********************************************************************/
/* Constructor for a given string.                                     */
/***********************************************************************/
JVALUE::JVALUE(PGLOBAL g, PCSZ strp) : JSON()
{
  Jsp = NULL;
//Val = AllocVal(g, TYPE_STRG);
  Strp = (char*)strp;
  DataType = TYPE_STRG;
  Nd = 0;
  Next = NULL;
  Del = false;
  Type = TYPE_JVAL;
} // end of JVALUE constructor

/***********************************************************************/
/* Set or reset all Jvalue members.                                    */
/***********************************************************************/
void JVALUE::Clear(void)
{
  Jsp = NULL; 
  Next = NULL; 
  Type = TYPE_JVAL; 
  Del = false; 
  Nd = 0; 
  DataType = TYPE_NULL;
} // end of Clear

/***********************************************************************/
/* Returns the type of the Value's value.                              */
/***********************************************************************/
JTYP JVALUE::GetValType(void)
{
  if (DataType == TYPE_JSON)
    return Jsp->GetType();
//else if (Val)
//  return Val->Type;
  else
    return DataType;

} // end of GetValType

/***********************************************************************/
/* Return the Value's Object value.                                    */
/***********************************************************************/
PJOB JVALUE::GetObject(void)
{
  if (DataType == TYPE_JSON && Jsp->GetType() == TYPE_JOB)
    return (PJOB)Jsp;

  return NULL;
} // end of GetObject

/***********************************************************************/
/* Return the Value's Array value.                                     */
/***********************************************************************/
PJAR JVALUE::GetArray(void)
{
  if (DataType == TYPE_JSON && Jsp->GetType() == TYPE_JAR)
    return (PJAR)Jsp;

  return NULL;
} // end of GetArray

/***********************************************************************/
/* Return the Value's as a Value class.                                */
/***********************************************************************/
PVAL JVALUE::GetValue(PGLOBAL g)
{
  PVAL valp = NULL;

  if (DataType != TYPE_JSON)
    if (DataType == TYPE_STRG)
      valp = AllocateValue(g, Strp, DataType, Nd);
    else
      valp = AllocateValue(g, &LLn, DataType, Nd);

  return valp;
} // end of GetValue

/***********************************************************************/
/* Return the Value's Integer value.                                   */
/***********************************************************************/
int JVALUE::GetInteger(void) {
  int n;

  switch (DataType) {
  case TYPE_INTG: n = N;           break;
  case TYPE_DBL:  n = (int)F;      break;
  case TYPE_DTM:
  case TYPE_STRG: n = atoi(Strp);  break;
  case TYPE_BOOL: n = (B) ? 1 : 0; break;
  case TYPE_BINT: n = (int)LLn;    break;
  default:
    n = 0;
  } // endswitch Type

  return n;
} // end of GetInteger

/***********************************************************************/
/* Return the Value's Big integer value.                               */
/***********************************************************************/
long long JVALUE::GetBigint(void)
{
  long long lln;

  switch (DataType) {
  case TYPE_BINT: lln = LLn;          break;
  case TYPE_INTG: lln = (long long)N; break;
  case TYPE_DBL:  lln = (long long)F; break;
  case TYPE_DTM:
  case TYPE_STRG: lln = atoll(Strp);  break;
  case TYPE_BOOL: lln = (B) ? 1 : 0;  break;
  default:
    lln = 0;
  } // endswitch Type

  return lln;
} // end of GetBigint

/***********************************************************************/
/* Return the Value's Double value.                                    */
/***********************************************************************/
double JVALUE::GetFloat(void)
{
  double d;

  switch (DataType) {
  case TYPE_DBL:  d = F;               break;
  case TYPE_BINT: d = (double)LLn;     break;
  case TYPE_INTG: d = (double)N;       break;
  case TYPE_DTM:
  case TYPE_STRG: d = atof(Strp);      break;
  case TYPE_BOOL: d = (B) ? 1.0 : 0.0; break;
  default:
    d = 0.0;
  } // endswitch Type

  return d;
} // end of GetFloat

/***********************************************************************/
/* Return the Value's String value.                                    */
/***********************************************************************/
PSZ JVALUE::GetString(PGLOBAL g, char *buff)
{
  char  buf[32];
  char *p = (buff) ? buff : buf;

  switch (DataType) {
  case TYPE_DTM:
  case TYPE_STRG:
    p = Strp;
    break;
  case TYPE_INTG:
    sprintf(p, "%d", N);
    break;
  case TYPE_BINT:
    sprintf(p, "%lld", LLn);
    break;
  case TYPE_DBL:
    sprintf(p, "%.*lf", Nd, F);
    break;
  case TYPE_BOOL:
    p = (char*)((B) ? "true" : "false");
    break;
  case TYPE_NULL:
    p = (char*)"null";
    break;
  default:
    p = NULL;
  } // endswitch Type


  return (p == buf) ? (char*)PlugDup(g, buf) : p;
} // end of GetString

/***********************************************************************/
/* Return the Value's String value.                                    */
/***********************************************************************/
PSZ JVALUE::GetText(PGLOBAL g, PSTRG text)
{
  if (DataType == TYPE_JSON)
    return Jsp->GetText(g, text);

	char buff[32];
  PSZ  s = (DataType == TYPE_NULL) ? NULL : GetString(g, buff);

	if (s)
		text->Append(s);
	else if (GetJsonNull())
		text->Append(GetJsonNull());

  return NULL;
} // end of GetText

void JVALUE::SetValue(PJSON jsp)
{
  if (DataType == TYPE_JSON && jsp->GetType() == TYPE_JVAL) {
    Jsp = jsp->GetJsp();
    Nd = ((PJVAL)jsp)->Nd;
    DataType = ((PJVAL)jsp)->DataType;
    //  Val = ((PJVAL)jsp)->GetVal();
  } else {
    Jsp = jsp;
    DataType = TYPE_JSON;
  } // endif Type

} // end of SetValue;

void JVALUE::SetValue(PGLOBAL g, PVAL valp)
{
//if (!Val)
//  Val = AllocVal(g, TYPE_VAL);

  if (!valp || valp->IsNull()) {
    DataType = TYPE_NULL;
  } else switch (valp->GetType()) {
  case TYPE_DATE:
		if (((DTVAL*)valp)->IsFormatted())
			Strp = PlugDup(g, valp->GetCharValue());
		else {
			char buf[32];

			Strp = PlugDup(g, valp->GetCharString(buf));
		}	// endif Formatted

		DataType = TYPE_DTM;
		break;
	case TYPE_STRING:
    Strp = PlugDup(g, valp->GetCharValue());
    DataType = TYPE_STRG;
    break;
  case TYPE_DOUBLE:
  case TYPE_DECIM:
    F = valp->GetFloatValue();

    if (IsTypeNum(valp->GetType()))
      Nd = valp->GetValPrec();

    DataType = TYPE_DBL;
    break;
  case TYPE_TINY:
    B = valp->GetTinyValue() != 0;
    DataType = TYPE_BOOL;
  case TYPE_INT:
    N = valp->GetIntValue();
    DataType = TYPE_INTG;
    break;
  case TYPE_BIGINT:
    LLn = valp->GetBigintValue();
    DataType = TYPE_BINT;
    break;
  default:
    snprintf(g->Message, sizeof(g->Message), "Unsupported typ %d\n", valp->GetType());
    throw(777);
  } // endswitch Type

} // end of SetValue

/***********************************************************************/
/* Set the Value's value as the given integer.                         */
/***********************************************************************/
void JVALUE::SetInteger(PGLOBAL g, int n)
{
  N = n;
  DataType = TYPE_INTG;
} // end of SetInteger

/***********************************************************************/
/* Set the Value's Boolean value as a tiny integer.                    */
/***********************************************************************/
void JVALUE::SetBool(PGLOBAL g, bool b)
{
  B = b;
  DataType = TYPE_BOOL;
} // end of SetTiny

/***********************************************************************/
/* Set the Value's value as the given big integer.                     */
/***********************************************************************/
void JVALUE::SetBigint(PGLOBAL g, long long ll)
{
  LLn = ll;
  DataType = TYPE_BINT;
} // end of SetBigint

/***********************************************************************/
/* Set the Value's value as the given DOUBLE.                          */
/***********************************************************************/
void JVALUE::SetFloat(PGLOBAL g, double f)
{
  F = f;
  Nd = GetDefaultPrec();
  DataType = TYPE_DBL;
} // end of SetFloat

/***********************************************************************/
/* Set the Value's value as the given string.                          */
/***********************************************************************/
void JVALUE::SetString(PGLOBAL g, PSZ s, int ci)
{
  Strp = s;
  Nd = ci;
  DataType = TYPE_STRG;
} // end of SetString

/***********************************************************************/
/* True when its JSON or normal value is null.                         */
/***********************************************************************/
bool JVALUE::IsNull(void)
{
  return (DataType == TYPE_JSON) ? Jsp->IsNull() : DataType == TYPE_NULL;
} // end of IsNull


/* ---------------------------- Class SWAP --------------------------- */

/***********************************************************************/
/* Replace all pointers by offsets or the opposite.                    */
/***********************************************************************/
void SWAP::SwapJson(PJSON jsp, bool move)
{
  if (move)
    MoffJson(jsp);
  else
    MptrJson((PJSON)MakeOff(Base, jsp));

  return;
} // end of SwapJson

/***********************************************************************/
/* Replace all pointers by offsets.                                    */
/***********************************************************************/
size_t SWAP::MoffJson(PJSON jsp) {
  size_t res = 0;

  if (jsp)
    switch (jsp->Type) {
    case TYPE_JAR:
      res = MoffArray((PJAR)jsp);
      break;
    case TYPE_JOB:
      res = MoffObject((PJOB)jsp);
      break;
    case TYPE_JVAL:
      res = MoffJValue((PJVAL)jsp);
      break;
    default:
      throw "Invalid json tree";
    } // endswitch Type

  return res;
} // end of MoffJson

/***********************************************************************/
/* Replace all array pointers by offsets.                              */
/***********************************************************************/
size_t SWAP::MoffArray(PJAR jarp)
{
  if (jarp->First) {
    for (int i = 0; i < jarp->Size; i++)
      jarp->Mvals[i] = (PJVAL)MakeOff(Base, jarp->Mvals[i]);

    jarp->Mvals = (PJVAL*)MakeOff(Base, jarp->Mvals);
    jarp->First = (PJVAL)MoffJValue(jarp->First);
    jarp->Last = (PJVAL)MakeOff(Base, jarp->Last);
  } // endif First

  return MakeOff(Base, jarp);
} // end of MoffArray

/***********************************************************************/
/* Replace all object pointers by offsets.                             */
/***********************************************************************/
size_t SWAP::MoffObject(PJOB jobp) {
  if (jobp->First) {
    jobp->First = (PJPR)MoffPair(jobp->First);
    jobp->Last = (PJPR)MakeOff(Base, jobp->Last);
  } // endif First

  return MakeOff(Base, jobp);
} // end of MoffObject

/***********************************************************************/
/* Replace all pair pointers by offsets.                               */
/***********************************************************************/
size_t SWAP::MoffPair(PJPR jpp) {
  jpp->Key = (PCSZ)MakeOff(Base, (void*)jpp->Key);

  if (jpp->Val)
    jpp->Val = (PJVAL)MoffJValue(jpp->Val);

  if (jpp->Next)
    jpp->Next = (PJPR)MoffPair(jpp->Next);

  return MakeOff(Base, jpp);
} // end of MoffPair

/***********************************************************************/
/* Replace all jason value pointers by offsets.                        */
/***********************************************************************/
size_t SWAP::MoffJValue(PJVAL jvp) {
  if (!jvp->Del) {
    if (jvp->DataType == TYPE_JSON)
      jvp->Jsp = (PJSON)MoffJson(jvp->Jsp);
    else if (jvp->DataType == TYPE_STRG)
      jvp->Strp = (PSZ)MakeOff(Base, (jvp->Strp));

//  if (jvp->Val)
//    jvp->Val = (PVL)MoffVal(jvp->Val);

  } // endif Del

  if (jvp->Next)
    jvp->Next = (PJVAL)MoffJValue(jvp->Next);

  return MakeOff(Base, jvp);
} // end of MoffJValue

#if 0
/***********************************************************************/
/* Replace string pointers by offset.                                  */
/***********************************************************************/
size_t SWAP::MoffVal(PVL vlp) {
  if (vlp->Type == TYPE_STRG)
    vlp->Strp = (PSZ)MakeOff(Base, (vlp->Strp));

  return MakeOff(Base, vlp);
} // end of MoffVal
#endif // 0

/***********************************************************************/
/* Replace all offsets by pointers.                                    */
/***********************************************************************/
PJSON SWAP::MptrJson(PJSON ojp) {      // ojp is an offset
  PJSON jsp = (PJSON)MakePtr(Base, (size_t)ojp);

  if (ojp)
    switch (jsp->Type) {
    case TYPE_JAR:
      jsp = MptrArray((PJAR)ojp);
      break;
    case TYPE_JOB:
      jsp = MptrObject((PJOB)ojp);
      break;
    case TYPE_JVAL:
      jsp = MptrJValue((PJVAL)ojp);
      break;
    default:
      throw "Invalid json tree";
    } // endswitch Type

  return jsp;
} // end of MptrJson

/***********************************************************************/
/* Replace all array offsets by pointers.                              */
/***********************************************************************/
PJAR SWAP::MptrArray(PJAR ojar) {
  PJAR jarp = (PJAR)MakePtr(Base, (size_t)ojar);

  jarp = (PJAR)new((long long)jarp) JARRAY(0);

  if (jarp->First) {
    jarp->Mvals = (PJVAL*)MakePtr(Base, (size_t)jarp->Mvals);

    for (int i = 0; i < jarp->Size; i++)
      jarp->Mvals[i] = (PJVAL)MakePtr(Base, (size_t)jarp->Mvals[i]);

    jarp->First = (PJVAL)MptrJValue(jarp->First);
    jarp->Last = (PJVAL)MakePtr(Base, (size_t)jarp->Last);
  } // endif First

  return jarp;
} // end of MptrArray

/***********************************************************************/
/* Replace all object offsets by pointers.                             */
/***********************************************************************/
PJOB SWAP::MptrObject(PJOB ojob) {
  PJOB jobp = (PJOB)MakePtr(Base, (size_t)ojob);

  jobp = (PJOB)new((long long)jobp) JOBJECT(0);

  if (jobp->First) {
    jobp->First = (PJPR)MptrPair(jobp->First);
    jobp->Last = (PJPR)MakePtr(Base, (size_t)jobp->Last);
  } // endif First

  return jobp;
} // end of MptrObject

/***********************************************************************/
/* Replace all pair offsets by pointers.                               */
/***********************************************************************/
PJPR SWAP::MptrPair(PJPR ojp) {
  PJPR jpp = (PJPR)MakePtr(Base, (size_t)ojp);

  jpp->Key = (PCSZ)MakePtr(Base, (size_t)jpp->Key);

  if (jpp->Val)
    jpp->Val = (PJVAL)MptrJValue(jpp->Val);

  if (jpp->Next)
    jpp->Next = (PJPR)MptrPair(jpp->Next);

  return jpp;
} // end of MptrPair

/***********************************************************************/
/* Replace all value offsets by pointers.                              */
/***********************************************************************/
PJVAL SWAP::MptrJValue(PJVAL ojv) {
  PJVAL jvp = (PJVAL)MakePtr(Base, (size_t)ojv);

  jvp = (PJVAL)new((long long)jvp) JVALUE(0);

  if (!jvp->Del) {
    if (jvp->DataType == TYPE_JSON)
      jvp->Jsp = (PJSON)MptrJson(jvp->Jsp);
    else if (jvp->DataType == TYPE_STRG)
      jvp->Strp = (PSZ)MakePtr(Base, (size_t)jvp->Strp);

//  if (jvp->Val)
//    jvp->Val = (PVL)MptrVal(jvp->Val);

  } // endif Del

  if (jvp->Next)
    jvp->Next = (PJVAL)MptrJValue(jvp->Next);

  return jvp;
} // end of MptrJValue

#if 0
/***********************************************************************/
/* Replace string offsets by a pointer.                                */
/***********************************************************************/
PVL SWAP::MptrVal(PVL ovl) {
  PVL vlp = (PVL)MakePtr(Base, (size_t)ovl);

  if (vlp->Type == TYPE_STRG)
    vlp->Strp = (PSZ)MakePtr(Base, (size_t)vlp->Strp);

  return vlp;
} // end of MptrValue
#endif // 0
