std.json dynamic initialization of JSONValue

Ali Çehreli acehreli at yahoo.com
Sat Mar 31 00:31:34 PDT 2012


On 12/01/2011 02:45 PM, Kai Meyer wrote:
 > I'm finding std.json extremely well written, with one glaring exception.
 >
 > I can't seem to figure out how to do this:
 >
 > JSONValue root = JSONValue(null, JSON_TYPE.OBJECT);
 > root.object["first_object"] = JSONValue(null, JSON_TYPE.OBJECT);
 > root.object["first_string"] = JSONValue("first_string", 
JSON_TYPE.STRING);
 >
 > which would decode to:
 >
 > {"first_object":{},"first_string":"first_string"}
 >
 > What I end up having to do is:
 > JSONValue root;
 > root.type = JSON_TYPE.OBJECT;
 > root.object["first_object"] = JSONValue();
 > root.object["first_object"].type = JSON_TYPE.OBJECT;
 > root.object["first_string"] = JSON_Value();
 > root.object["first_string"].type = JSON_TYPE.STRING;
 > root.object["first_string"].str = "first_string";
 >
 > That just feels like I'm doing it wrong. Is there a way to dynamically
 > initialize a JSONValue struct?

std.json doesn't provide help with constructing different JSON values.

 > If I try to intialize the JSONValue
 > object with anything other than simply null, or empty string, I either
 > get a compile error or a segfault at run-time.

That's because JSONValue has an anonymous union, which can only be 
initialized by their first member, and that happens to be 'str' for 
JSONValue:

   http://dlang.org/struct.html

<quote>
If there are anonymous unions in the struct, only the first member of 
the anonymous union can be initialized with a struct literal, and all 
subsequent non-overlapping fields are default initialized.
</quote>

 > root.object["first_object"] = JSONValue(null, JSON_TYPE.OBJECT);
 >
 > compile error:
 > Error: overlapping initialization for integer
 >
 > root.object["first_string"] = JSONValue("first_string");
 > run-time segfault.
 >
 > Any ideas?

std.json have been discussed recently at a different forum.[1] There, I 
have come up with the following code which simplifies constructing JSON 
objects by defining to!JSONValue():

import std.stdio;
import std.conv;
import std.json;
import std.traits;
import std.exception;
import std.string;

/* Observation: Neither to() is a function of this module, nor
  * JSONValue is its type. Is such a function template that combines
  * the two legitimate? Yes modules bring namespaces, so name
  * collisions can be avoided by fully qualifying names, it still feels
  * like std.json should provide this function template.
  *
  * (Aside: Although C++ forbids defining functions in the std
  * namespace, it is sometimes necessary to define the << operator for
  * std::pair of user types.)
  *
  * BUG: This function is lacks supports for associative arrays.
  */
JSONValue to(Target : JSONValue, T)(T value)
{
     JSONValue json;

     static if (isSomeString!T) {
         json.type = JSON_TYPE.STRING;
         json.str = std.conv.to!string(value);

     } else static if (is (T : long)) {
         static if (is (T == ulong)) {
             /* Because std.json uses the long type for JSON
              * 'INTEGER's, we protect against data loss with ulong
              * values. */
             enforce(value <= long.max,
                     format("Loss of data: %s value %s cannot be"
                            " represented as long!",
                            T.stringof, value));
         }

         json.type = JSON_TYPE.INTEGER;
         json.integer = value;

     } else static if (is (T : real)) {
         json.type = JSON_TYPE.FLOAT;
         json.floating = value;

     } else static if (isArray!T) {
         json.type = JSON_TYPE.ARRAY;

         foreach (eleman; value) {
             json.array ~= to!JSONValue(eleman);
         }

     } else static if (__traits(compiles, cast(JSONValue)value)) {
         json = cast(JSONValue)(value);

     } else {
         static assert(false,
                       "Cannot convert this type to JSONValue: "
                       ~ T.stringof);
     }

     return json;
}

unittest
{
     import std.typetuple;

     /* Test that we are protected against data loss. */
     bool isThrown = false;
     try {
         to!JSONValue(ulong.max);

     } catch (Exception) {
         isThrown = true;
     }
     enforce(isThrown, "Exception for ulong.max has not been thrown!");

     /* These types must be supported by to!JSONValue(). */
     alias TypeTuple!(
         byte, ubyte, short, ushort, int, uint, long, ulong,
         float, double, real,
         string, wstring, dstring, char[], wchar[], dchar[],
         int[])
         Types;

     foreach (Type; Types) {
         to!JSONValue(Type.init);
     }
}

struct Student
{
     string name;
     ulong id;
     uint[] grades;

     JSONValue opCast(T : JSONValue)() const @property
     {
         /*
          * TODO: It should be possible to simplify this function by
          * taking advantage of __traits(allMembers) and perhaps
          * mixin() by excluding the member functions by isCallable().
          *
          * A question remains: What if one of the members is of a type
          * that defines the opCall() operator? Would isCallable()
          * produce true for that data member as well and exclude it
          * from JSON representation?
          */
         JSONValue[string] members;
         members["name"] = to!JSONValue(name);
         members["id"] = to!JSONValue(id);
         members["grades"] = to!JSONValue(grades);

         JSONValue json;
         json.object = members;
         json.type = JSON_TYPE.OBJECT;
         return json;
     }
}

JSONValue JSONRoot()
{
     JSONValue json;
     json.type = JSON_TYPE.OBJECT;
     return json;
}

void main()
{
     auto students = [ Student("Ayşe", 12, [ 90, 100 ]),
                       Student("Başak", 34, [ 95, 99 ]) ];

     JSONValue root = JSONRoot();
     root.object["students"] = to!JSONValue(students);

     writeln(toJSON(&root));
}

Ali

[1] Ddili Turkish forum: http://ddili.org/forum/forum



More information about the Digitalmars-d-learn mailing list