• What is better, fancy or flat?

    From Paavo Helde@21:1/5 to All on Thu Jan 23 23:41:04 2025
    Today I am having a style question. Assume I have got a JSON serializer
    class with the following interface:

    class JSONBuilder {
    public:
    JSONBuilder& AddString(stringview key, stringview value);
    JSONBuilder& AddString(stringview value);
    JSONBuilder& AddNumber(stringview key, int value);

    JSONBuilder& BeginObject(stringview key);
    JSONBuilder& EndObject(stringview key);
    JSONBuilder& BeginArray(stringview key);
    JSONBuilder& EndArray(stringview key);

    template<class BUILDER>
    JSONBuilder& AddObject(stringview key, BUILDER objectBuilder) {
    BeginObject(key);
    objectBuilder(*this);
    EndObject(key);
    return *this;
    }

    template<class BUILDER>
    JSONBuilder& AddArray(stringview key, BUILDER arrayBuilder) {
    BeginArray(key);
    arrayBuilder(*this);
    EndArray(key);
    return *this;
    }
    // ...
    };

    Should I use the "low-level" BeginObject/EndObject or "high-level"
    AddObject? At first I thought "high-level" and structured surely must be better, with code indentation following the output structure and
    automatic closing of constructs. But then I looked at the resulting code
    and now I am not so convinced any more:

    /// Generates a `requestBody` object for an OpenAPI document.
    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
    builder
    .AddObject("requestBody", [&](JSONBuilder& builder) {
    builder
    .AddString("required", "true")
    .AddObject("content", [&](JSONBuilder& builder) {
    builder
    .AddObject("application/json", [&](JSONBuilder& builder) {
    builder
    .AddObject("schema", [&](JSONBuilder& builder) {
    builder
    .AddString("type", "object")
    .AddObject("properties", [&](JSONBuilder& builder) {
    builder
    .AddObject("control", [&](JSONBuilder& builder) {
    builder.AddString("type", "object");

    std::vector<std::string> requiredParams;
    builder.AddObject("properties", [&](JSONBuilder& builder) {

    // Iterate over all API parameters and add them to the requestBody
    for (const auto& param : parameters) {
    if (HasFlag(param.endpoints_, ep) && param.direction_
    == direction::request) {
    builder.AddObject(param.name_, [&](JSONBuilder&
    builder) {
    builder
    .AddString("type", ToString(param.type_))
    .AddString("description", param.description_);
    if (param.presence_ == presence::required) {
    requiredParams.push_back(std::string(param.name_));
    }
    });
    }
    }
    });
    builder.AddArray("required", [&](JSONBuilder& builder) {
    for (const auto& name : requiredParams) {
    builder.AddString(name);
    }
    });
    builder
    .AddObject("params", [&](JSONBuilder& builder) {
    builder
    .AddString("type", "object")
    .AddString("description", "Additional parameters for
    the service. The structure of this object is service-specific.");
    });
    });
    });
    });
    });
    });
    });
    }

    Not sure if this reminds me more Perl or more LISP...

    For comparison, the "low-level" usage would looks much more tamed:

    /// Generates a `requestBody` object for an OpenAPI endpoint.
    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
    builder
    .BeginObject("requestBody")
    .AddString("required", "true")
    .BeginObject("content")
    .BeginObject("application/json")
    .BeginObject("schema")
    .AddString("type", "object")
    .BeginObject("properties")
    .BeginObject("control")
    .AddString("type", "object")
    .BeginObject("properties");

    std::vector<std::string> requiredParams;

    // Iterate over all parameters and add them to the requestBody
    for (const auto& param : parameters) {
    if (HasFlag(param.endpoints_, ep) && param.direction_ == direction::request) {
    builder
    .BeginObject(param.name_)
    .AddString("type", ToString(param.type_))
    .AddString("description", param.description_)
    .EndObject(param.name_);
    if (param.presence_ == presence::required) {
    requiredParams.push_back(std::string(param.name_));
    }
    }
    }
    builder.EndObject("properties");

    builder.BeginArray("required");
    for (const auto& name : requiredParams) {
    builder.AddString(name);
    }
    builder
    .EndArray("required")
    .EndObject("control")

    .BeginObject("params")
    .AddString("type", "object")
    .AddString("description", "Additional parameters for the service.
    The structure of this object is service-specific.")
    .EndObject("params")

    .EndObject("properties")
    .EndObject("schema")
    .EndObject("application/json")
    .EndObject("content")
    .EndObject("requestBody");
    }

    Here, the most challenging task is to get the end tags matching. But
    this can be checked automatically by the implementation.

    Any thoughts?

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to Paavo Helde on Fri Jan 24 10:16:09 2025
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON serializer
    class with the following interface:

    Meanwhile, I have figured out yet another way to do the same, inspired
    by std::format(). This now looks more like PHP...


    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {

    // Build the variable pieces beforehand
    JSONBuilder properties, required;
    properties.BeginObject("properties");
    required.BeginArray("required");

    // Iterate over all parameters and add them to the
    for (const auto& param : parameters) {
    if (HasFlag(param.endpoints_, ep) && param.direction_ == direction::request) {
    properties
    .BeginObject(param.name_)
    .AddString("type", ToString(param.type_))
    .AddString("description", param.description_)
    .EndObject(param.name_);
    if (param.presence_ == presence::required) {
    required.AddString(param.name_);
    }
    }
    }

    // Inject the variable pieces as {0} and {1} in the constant
    // template piece of the openapi document:

    builder.AddFormatted(R"__(
    "requestBody": {
    "required": true,
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "control": {
    "type": "object",
    {0},
    {1}
    },
    "params": {
    "type": "object",
    "description": "Parameters for the command"
    }
    }
    }
    }
    }
    })__", properties.str(), required.str());
    }

    In all three cases, the produced output result ought to be the same
    (starting from `requestBody` in this example):

    {
    "openapi": "3.1.0",
    "info": {
    "title": "{redacted}",
    "description": "{reducted}",
    "version": "1.0.0"
    },
    "paths": {
    "/basepath/service/test1/start": {
    "post": {
    "summary": "Start a new test1 session and return immediately.",
    "requestBody": {
    "required": true,
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "control": {
    "type": "object",
    "properties": {
    "authorization": {
    "type": "string",
    "description": "Bearer token for authorization"
    },
    "user": {
    "type": "string",
    "description": "Username"
    },
    "client": {
    "type": "string",
    "description": "Client identifier"
    },
    // ... ... ...
    },
    "required": [
    "user",
    ]
    },
    "params": {
    "type": "object",
    "description": "Parameters for the command"
    }
    }
    }
    }
    }
    },
    // ... ... ...







    class JSONBuilder {
    public:
      JSONBuilder& AddString(stringview key, stringview value);
      JSONBuilder& AddString(stringview value);
      JSONBuilder& AddNumber(stringview key, int value);

      JSONBuilder& BeginObject(stringview key);
      JSONBuilder& EndObject(stringview key);
      JSONBuilder& BeginArray(stringview key);
      JSONBuilder& EndArray(stringview key);

      template<class BUILDER>
      JSONBuilder& AddObject(stringview key, BUILDER objectBuilder) {
        BeginObject(key);
        objectBuilder(*this);
        EndObject(key);
        return *this;
      }

      template<class BUILDER>
      JSONBuilder& AddArray(stringview key, BUILDER arrayBuilder) {
        BeginArray(key);
        arrayBuilder(*this);
        EndArray(key);
        return *this;
      }
      // ...
    };

    Should I use the "low-level" BeginObject/EndObject or "high-level"
    AddObject? At first I thought "high-level" and structured surely must be better, with code indentation following the output structure and
    automatic closing of constructs. But then I looked at the resulting code
    and now I am not so convinced any more:

    /// Generates a `requestBody` object for an OpenAPI document.
    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
      builder
        .AddObject("requestBody", [&](JSONBuilder& builder) {
        builder
          .AddString("required", "true")
          .AddObject("content", [&](JSONBuilder& builder) {
          builder
            .AddObject("application/json", [&](JSONBuilder& builder) {
            builder
              .AddObject("schema", [&](JSONBuilder& builder) {
              builder
                .AddString("type", "object")
                .AddObject("properties", [&](JSONBuilder& builder) {
                builder
                  .AddObject("control", [&](JSONBuilder& builder) {
                  builder.AddString("type", "object");

                  std::vector<std::string> requiredParams;
                  builder.AddObject("properties", [&](JSONBuilder& builder) {

                    // Iterate over all API parameters and add them to the
    requestBody
                    for (const auto& param : parameters) {
                      if (HasFlag(param.endpoints_, ep) && param.direction_
    == direction::request) {
                        builder.AddObject(param.name_, [&](JSONBuilder&
    builder) {
                          builder
                            .AddString("type", ToString(param.type_))
                            .AddString("description", param.description_);
                          if (param.presence_ == presence::required) {

    requiredParams.push_back(std::string(param.name_));
                          }
                          });
                      }
                    }
                    });
                  builder.AddArray("required", [&](JSONBuilder& builder) {
                    for (const auto& name : requiredParams) {
                      builder.AddString(name);
                    }
                    });
                  builder
                    .AddObject("params", [&](JSONBuilder& builder) {
                    builder
                      .AddString("type", "object")
                      .AddString("description", "Additional parameters for
    the service. The structure of this object is service-specific.");
                      });
                    });
                  });
                });
              });
            });
          });
    }

    Not sure if this reminds me more Perl or more LISP...

    For comparison, the "low-level" usage would looks much more tamed:

      /// Generates a `requestBody` object for an OpenAPI endpoint.
      void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
        builder
          .BeginObject("requestBody")
          .AddString("required", "true")
          .BeginObject("content")
          .BeginObject("application/json")
          .BeginObject("schema")
          .AddString("type", "object")
          .BeginObject("properties")
          .BeginObject("control")
          .AddString("type", "object")
          .BeginObject("properties");

        std::vector<std::string> requiredParams;

        // Iterate over all parameters and add them to the requestBody
        for (const auto& param : parameters) {
          if (HasFlag(param.endpoints_, ep) && param.direction_ == direction::request) {
            builder
              .BeginObject(param.name_)
              .AddString("type", ToString(param.type_))
              .AddString("description", param.description_)
              .EndObject(param.name_);
            if (param.presence_ == presence::required) {
              requiredParams.push_back(std::string(param.name_));
            }
          }
        }
        builder.EndObject("properties");

        builder.BeginArray("required");
        for (const auto& name : requiredParams) {
          builder.AddString(name);
        }
        builder
          .EndArray("required")
          .EndObject("control")

          .BeginObject("params")
          .AddString("type", "object")
          .AddString("description", "Additional parameters for the service. The structure of this object is service-specific.")
          .EndObject("params")

          .EndObject("properties")
          .EndObject("schema")
          .EndObject("application/json")
          .EndObject("content")
          .EndObject("requestBody");
      }

    Here, the most challenging task is to get the end tags matching. But
    this can be checked automatically by the implementation.

    Any thoughts?



    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From David Brown@21:1/5 to Paavo Helde on Fri Jan 24 11:05:27 2025
    On 24/01/2025 09:16, Paavo Helde wrote:
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON
    serializer class with the following interface:

    Meanwhile, I have figured out yet another way to do the same, inspired
    by std::format(). This now looks more like PHP...



    I think it is going to look ugly and complicated no matter how you do it
    here. So your prime concern, IMHO, should be maintainability. If you
    can make a code structure that closely resembles the structure of the
    JSON objects, it will be far easier to change it later when the JSON
    object changes.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to David Brown on Sat Jan 25 00:07:29 2025
    On 24.01.2025 12:05, David Brown wrote:
    On 24/01/2025 09:16, Paavo Helde wrote:
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON
    serializer class with the following interface:

    Meanwhile, I have figured out yet another way to do the same, inspired
    by std::format(). This now looks more like PHP...



    I think it is going to look ugly and complicated no matter how you do it here.  So your prime concern, IMHO, should be maintainability.  If you
    can make a code structure that closely resembles the structure of the
    JSON objects, it will be far easier to change it later when the JSON
    object changes.

    Yes, the best approach as always would be to add another abstraction
    layer and define special OpenAPI building classes instead of generic
    JSON builders. But that looks as a lot of work with little benefit.
    Probably there are already some libraries out there doing that, but
    using them would add another dependency.

    In the end, I decided to go with the simpler BeginObject+EndObject
    approach. Seems to be manageable, the key insight is always to write the EndObject line before writing any of the content between.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From [email protected]@21:1/5 to All on Sat Jan 25 17:04:23 2025
    On Sat, 25 Jan 2025 00:07:29 +0200
    Paavo Helde <[email protected]> gabbled:
    On 24.01.2025 12:05, David Brown wrote:
    On 24/01/2025 09:16, Paavo Helde wrote:
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON
    serializer class with the following interface:

    Meanwhile, I have figured out yet another way to do the same, inspired
    by std::format(). This now looks more like PHP...



    I think it is going to look ugly and complicated no matter how you do it
    here.  So your prime concern, IMHO, should be maintainability.  If you
    can make a code structure that closely resembles the structure of the
    JSON objects, it will be far easier to change it later when the JSON
    object changes.

    Yes, the best approach as always would be to add another abstraction
    layer and define special OpenAPI building classes instead of generic
    JSON builders. But that looks as a lot of work with little benefit.
    Probably there are already some libraries out there doing that, but
    using them would add another dependency.

    In the end, I decided to go with the simpler BeginObject+EndObject
    approach. Seems to be manageable, the key insight is always to write the >EndObject line before writing any of the content between.

    Personal preference but I prefer just to create a std::string containing
    json (or XML) and convert it to an object at the end. That way you can not
    only see directly what you're going to get but you can use standard C++ functions to modify it if required.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Paavo Helde@21:1/5 to [email protected] on Sat Jan 25 23:53:06 2025
    On 25.01.2025 19:04, [email protected] wrote:
    On Sat, 25 Jan 2025 00:07:29 +0200
    Paavo Helde <[email protected]> gabbled:
    On 24.01.2025 12:05, David Brown wrote:
    On 24/01/2025 09:16, Paavo Helde wrote:
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON
    serializer class with the following interface:

    Meanwhile, I have figured out yet another way to do the same,
    inspired by std::format(). This now looks more like PHP...



    I think it is going to look ugly and complicated no matter how you do
    it here.  So your prime concern, IMHO, should be maintainability.  If
    you can make a code structure that closely resembles the structure of
    the JSON objects, it will be far easier to change it later when the
    JSON object changes.

    Yes, the best approach as always would be to add another abstraction
    layer and define special OpenAPI building classes instead of generic
    JSON builders. But that looks as a lot of work with little benefit.
    Probably there are already some libraries out there doing that, but
    using them would add another dependency.

    In the end, I decided to go with the simpler BeginObject+EndObject
    approach. Seems to be manageable, the key insight is always to write
    the EndObject line before writing any of the content between.

    Personal preference but I prefer just to create a std::string containing
    json (or XML) and convert it to an object at the end. That way you can not only see directly what you're going to get but you can use standard C++ functions to modify it if required.

    For once, I agree with you here. My JSONBuilder also builds a
    std::string directly, that's the simplest and fastest approach. No need
    to convert it to anything as it will be just served as an HTTP packet
    anyway. Still, I parse the result with a JSON parser in Debug build,
    just to make sure I have generated valid JSON.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From [email protected]@21:1/5 to All on Sun Jan 26 09:03:53 2025
    On Sat, 25 Jan 2025 23:53:06 +0200
    Paavo Helde <[email protected]> wibbled:
    On 25.01.2025 19:04, [email protected] wrote:
    Personal preference but I prefer just to create a std::string containing
    json (or XML) and convert it to an object at the end. That way you can not >> only see directly what you're going to get but you can use standard C++
    functions to modify it if required.

    For once, I agree with you here. My JSONBuilder also builds a
    std::string directly, that's the simplest and fastest approach. No need
    to convert it to anything as it will be just served as an HTTP packet
    anyway. Still, I parse the result with a JSON parser in Debug build,
    just to make sure I have generated valid JSON.

    Sensible. Creating json is the easy bit. Its when you've downloaded some and need to parse it that these libraries prove their worth.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Stuart Redmann@21:1/5 to Paavo Helde on Mon Jan 27 08:09:24 2025
    Paavo Helde <[email protected]> wrote:
    On 23.01.2025 23:41, Paavo Helde wrote:

    Today I am having a style question. Assume I have got a JSON serializer
    class with the following interface:

    Meanwhile, I have figured out yet another way to do the same, inspired
    by std::format(). This now looks more like PHP...


    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {

    // Build the variable pieces beforehand
    JSONBuilder properties, required;
    properties.BeginObject("properties");
    required.BeginArray("required");

    // Iterate over all parameters and add them to the
    for (const auto& param : parameters) {
    if (HasFlag(param.endpoints_, ep) && param.direction_ == direction::request) {
    properties
    .BeginObject(param.name_)
    .AddString("type", ToString(param.type_))
    .AddString("description", param.description_)
    .EndObject(param.name_);
    if (param.presence_ == presence::required) {
    required.AddString(param.name_);
    }
    }
    }

    // Inject the variable pieces as {0} and {1} in the constant
    // template piece of the openapi document:

    builder.AddFormatted(R"__(
    "requestBody": {
    "required": true,
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "control": {
    "type": "object",
    {0},
    {1}
    },
    "params": {
    "type": "object",
    "description": "Parameters for the command"
    }
    }
    }
    }
    }
    })__", properties.str(), required.str());
    }

    In all three cases, the produced output result ought to be the same
    (starting from `requestBody` in this example):

    {
    "openapi": "3.1.0",
    "info": {
    "title": "{redacted}",
    "description": "{reducted}",
    "version": "1.0.0"
    },
    "paths": {
    "/basepath/service/test1/start": {
    "post": {
    "summary": "Start a new test1 session and return immediately.",
    "requestBody": {
    "required": true,
    "content": {
    "application/json": {
    "schema": {
    "type": "object",
    "properties": {
    "control": {
    "type": "object",
    "properties": {
    "authorization": {
    "type": "string",
    "description": "Bearer token for authorization"
    },
    "user": {
    "type": "string",
    "description": "Username"
    },
    "client": {
    "type": "string",
    "description": "Client identifier"
    },
    // ... ... ...
    },
    "required": [
    "user",
    ]
    },
    "params": {
    "type": "object",
    "description": "Parameters for the command"
    }
    }
    }
    }
    }
    },
    // ... ... ...







    class JSONBuilder {
    public:
      JSONBuilder& AddString(stringview key, stringview value);
      JSONBuilder& AddString(stringview value);
      JSONBuilder& AddNumber(stringview key, int value);

      JSONBuilder& BeginObject(stringview key);
      JSONBuilder& EndObject(stringview key);
      JSONBuilder& BeginArray(stringview key);
      JSONBuilder& EndArray(stringview key);

      template<class BUILDER>
      JSONBuilder& AddObject(stringview key, BUILDER objectBuilder) {
        BeginObject(key);
        objectBuilder(*this);
        EndObject(key);
        return *this;
      }

      template<class BUILDER>
      JSONBuilder& AddArray(stringview key, BUILDER arrayBuilder) {
        BeginArray(key);
        arrayBuilder(*this);
        EndArray(key);
        return *this;
      }
      // ...
    };

    Should I use the "low-level" BeginObject/EndObject or "high-level"
    AddObject? At first I thought "high-level" and structured surely must be
    better, with code indentation following the output structure and
    automatic closing of constructs. But then I looked at the resulting code
    and now I am not so convinced any more:

    /// Generates a `requestBody` object for an OpenAPI document.
    void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
      builder
        .AddObject("requestBody", [&](JSONBuilder& builder) {
        builder
          .AddString("required", "true")
          .AddObject("content", [&](JSONBuilder& builder) {
          builder
            .AddObject("application/json", [&](JSONBuilder& builder) {
            builder
              .AddObject("schema", [&](JSONBuilder& builder) {
              builder
                .AddString("type", "object")
                .AddObject("properties", [&](JSONBuilder& builder) { >>             builder
                  .AddObject("control", [&](JSONBuilder& builder) { >>               builder.AddString("type", "object");

                  std::vector<std::string> requiredParams;
                  builder.AddObject("properties", [&](JSONBuilder& builder) {

                    // Iterate over all API parameters and add them to the
    requestBody
                    for (const auto& param : parameters) {
                      if (HasFlag(param.endpoints_, ep) && param.direction_
    == direction::request) {
                        builder.AddObject(param.name_, [&](JSONBuilder&
    builder) {
                          builder
                            .AddString("type", ToString(param.type_))
                            .AddString("description", param.description_);
                          if (param.presence_ == presence::required) {

    requiredParams.push_back(std::string(param.name_));
                          }
                          });
                      }
                    }
                    });
                  builder.AddArray("required", [&](JSONBuilder& builder) {
                    for (const auto& name : requiredParams) {
                      builder.AddString(name);
                    }
                    });
                  builder
                    .AddObject("params", [&](JSONBuilder& builder) {
                    builder
                      .AddString("type", "object")
                      .AddString("description", "Additional parameters for
    the service. The structure of this object is service-specific.");
                      });
                    });
                  });
                });
              });
            });
          });
    }

    Not sure if this reminds me more Perl or more LISP...

    For comparison, the "low-level" usage would looks much more tamed:

      /// Generates a `requestBody` object for an OpenAPI endpoint.
      void GenerateRequestBody(JSONBuilder& builder, endpoint ep) {
        builder
          .BeginObject("requestBody")
          .AddString("required", "true")
          .BeginObject("content")
          .BeginObject("application/json")
          .BeginObject("schema")
          .AddString("type", "object")
          .BeginObject("properties")
          .BeginObject("control")
          .AddString("type", "object")
          .BeginObject("properties");

        std::vector<std::string> requiredParams;

        // Iterate over all parameters and add them to the requestBody
        for (const auto& param : parameters) {
          if (HasFlag(param.endpoints_, ep) && param.direction_ ==
    direction::request) {
            builder
              .BeginObject(param.name_)
              .AddString("type", ToString(param.type_))
              .AddString("description", param.description_)
              .EndObject(param.name_);
            if (param.presence_ == presence::required) {
              requiredParams.push_back(std::string(param.name_));
            }
          }
        }
        builder.EndObject("properties");

        builder.BeginArray("required");
        for (const auto& name : requiredParams) {
          builder.AddString(name);
        }
        builder
          .EndArray("required")
          .EndObject("control")

          .BeginObject("params")
          .AddString("type", "object")
          .AddString("description", "Additional parameters for the service. >> The structure of this object is service-specific.")
          .EndObject("params")

          .EndObject("properties")
          .EndObject("schema")
          .EndObject("application/json")
          .EndObject("content")
          .EndObject("requestBody");
      }

    Here, the most challenging task is to get the end tags matching. But
    this can be checked automatically by the implementation.

    Any thoughts?

    I like the AddObject API better. It yields valid JSON at all times, whereas
    the BeginObject/EndObject needs runtime checks.

    You might also consider an API that allows to create JSONFragments that can
    be joined together later, something like MSXML. The user would then be able
    to pass around JSONFragment objects around to methods that may attach some properties to the passed fragment. Or add elements to an JSONArray. That
    way you could rewrite your AddObject code as flat code without the IMHO
    rather nasty deep nesting.

    Regards
    Stuart

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)