Lost Souls: Free Text-Based RPG
Home » Grimoire » JSON Module

Grimoire: LPC JSON



Our JSON module provides LPC-level JSON encoding and decoding and is compatible with both FluffOS and LDMud drivers.

Tarball: lpc-json-1.0.3.tar.gz

Doc File: json_encode

json_encode - serializes an LPC value as JSON

SYNOPSIS
    string json_encode(mixed value)

DESCRIPTION
    Returns a string attempting to represent the passed value in JSON
    (JavaScript Object Notation; see http://json.org/).

    The data types which map well from LPC to JSON are single-value mappings
    with string keys (which are represented as objects), arrays, strings,
    ints, and floats.

    Many LPC values, including objects, closures, quoted arrays, and symbols,
    have no valid JSON representation.  json_encode() uses "null" for these.
    Non-string mapping keys also have no valid JSON representation; these
    are skipped.

    json_encode() represents zero-width mappings as JSON arrays and
    multivalue mappings as if the value sets were arrays of values.  These
    representations are inexact and do not result in restoration of the
    original value via json_decode().

    If json_encode() encounters a self-referential data structure, it will
    use "null" for inner references to the structure (instead of recursing
    infinitely).

EXAMPLES
    json_encode(([ "name" : "Bob" ])) => "{\"name\":\"Bob\"}"
    
    json_encode(({ 1, 2, 0.5 })) => "[1,2,0.5]"

    mixed * selfref = ({ 1, 2, 3, 0 });
    selfref[3] = selfref;
    json_encode(selfref) => "[1,2,3,null]"

SEE ALSO
    json_decode(sefun), save_value(efun), restore_value(efun)

Doc File: json_decode

json_decode - deserializes a JSON string to an LPC value

SYNOPSIS
    mixed json_decode(string json)

DESCRIPTION
    Converts the passed JSON (JavaScript Object Notation; see http://json.org/)
    string to an LPC value and returns it.

    The JSON values that map inexactly to LPC are true (which maps to 1), false
    (which maps to 0) and null (which also maps to 0).  As most JSON platforms
    use higher-precision floating-point numbers than most LPC platforms, loss
    of floating-point precision should also be anticipated.  Also, as JSON
    strings are UTF-8 encoded, an LPC driver which isn't prepared to handle
    UTF-8 may encounter issues with them.

EXAMPLES
    json_decode("{\"name\":\"Bob\"}") => ([ "name" : "Bob" ])

    json_decode("[1,2,0.5]") => ({ 1, 2, 0.5 })

SEE ALSO
    json_encode(sefun), save_value(efun), restore_value(efun)

Source File: json.c

/**
 * json.c
 *
 * LPC support functions for JSON serialization and deserialization.
 * Attempts to be compatible with reasonably current FluffOS and LDMud
 * drivers, with at least a gesture or two toward compatibility with
 * older drivers.
 *
 * Web site for updates: http://lostsouls.org/grimoire_json
 *
 * mixed json_decode(string text)
 *     Deserializes JSON into an LPC value.
 *
 * string json_encode(mixed value)
 *     Serializes an LPC value into JSON text.
 *
 * v1.0: initial release
 * v1.0.1: fix for handling of \uXXXX on MudOS
 * v1.0.2: define array keyword for LDMud & use it consistently
 *
 * LICENSE
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Thaumatic Systems, LLC
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */


#ifdef MUDOS
#define strstr                      strsrch
#define raise_error                 error
#define member(x, y)                member_array(y, x)
#define to_string(x)                ("" + (x))
#else // MUDOS
#include <lpctypes.h>
#define replace_string(x, y, z)     implode(explode((x), (y)), (z))
#define in                          :
#ifndef array
#define array                       *
#endif // !array
#endif // MUDOS

#define JSON_DECODE_PARSE_TEXT      0
#define JSON_DECODE_PARSE_POS       1
#define JSON_DECODE_PARSE_LINE      2
#define JSON_DECODE_PARSE_CHAR      3

#define JSON_DECODE_PARSE_FIELDS    4

private mixed json_decode_parse_value(mixed array parse);
private varargs mixed json_decode_parse_string(mixed array parse, int initiator_checked);

private void json_decode_parse_next_char(mixed array parse) {
    parse[JSON_DECODE_PARSE_POS]++;
    parse[JSON_DECODE_PARSE_CHAR]++;
}

private void json_decode_parse_next_chars(mixed array parse, int num) {
    parse[JSON_DECODE_PARSE_POS] += num;
    parse[JSON_DECODE_PARSE_CHAR] += num;
}

private void json_decode_parse_next_line(mixed array parse) {
    parse[JSON_DECODE_PARSE_POS]++;
    parse[JSON_DECODE_PARSE_LINE]++;
    parse[JSON_DECODE_PARSE_CHAR] = 1;
}

private int json_decode_hexdigit(int ch) {
    switch(ch) {
    case '0'    :
        return 0;
    case '1'    :
    case '2'    :
    case '3'    :
    case '4'    :
    case '5'    :
    case '6'    :
    case '7'    :
    case '8'    :
    case '9'    :
        return ch - '0';
    case 'a'    :
    case 'A'    :
        return 10;
    case 'b'    :
    case 'B'    :
        return 11;
    case 'c'    :
    case 'C'    :
        return 12;
    case 'd'    :
    case 'D'    :
        return 13;
    case 'e'    :
    case 'E'    :
        return 14;
    case 'f'    :
    case 'F'    :
        return 15;
    }
    return -1;
}

private varargs int json_decode_parse_at_token(mixed array parse, string token, int start) {
    int i, j;
    for(i = start, j = strlen(token); i < j; i++)
        if(parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS] + i] != token[i])
            return 0;
    return 1;
}

private varargs void json_decode_parse_error(mixed array parse, string msg, int ch) {
    if(ch)
        msg = sprintf("%s, '%c'", msg, ch);
    msg = sprintf("%s @ line %d char %d\n", msg, parse[JSON_DECODE_PARSE_LINE], parse[JSON_DECODE_PARSE_CHAR]);
    raise_error(msg);
}

private mixed json_decode_parse_object(mixed array parse) {
    mapping out = ([]);
    int done = 0;
    mixed key, value;
    int found_non_whitespace, found_sep, found_comma;
    json_decode_parse_next_char(parse);
    if(parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]] == '}') {
        done = 1;
        json_decode_parse_next_char(parse);
    }
    while(!done) {
        found_non_whitespace = 0;
        while(!found_non_whitespace) {
            switch(parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]]) {
            case 0      :
                json_decode_parse_error(parse, "Unexpected end of data");
            case ' '    :
            case '\t'   :
            case '\r'   :
                json_decode_parse_next_char(parse);
                break;
#ifdef MUDOS
            case 0x0c   :
#else // MUDOS
            case '\f'   :
#endif // MUDOS
            case '\n'   :
                json_decode_parse_next_line(parse);
                break;
            default     :
                found_non_whitespace = 1;
                break;
            }
        }
        key = json_decode_parse_string(parse);
        found_sep = 0;
        while(!found_sep) {
            int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
            switch(ch) {
            case 0      :
                json_decode_parse_error(parse, "Unexpected end of data");
            case ':'    :
                found_sep = 1;
                json_decode_parse_next_char(parse);
                break;
            case ' '    :
            case '\t'   :
            case '\r'   :
                json_decode_parse_next_char(parse);
                break;
#ifdef MUDOS
            case 0x0c   :
#else // MUDOS
            case '\f'   :
#endif // MUDOS
            case '\n'   :
                json_decode_parse_next_line(parse);
                break;
            default     :
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        }
        value = json_decode_parse_value(parse);
        found_comma = 0;
        while(!found_comma && !done) {
            int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
            switch(ch) {
            case 0      :
                json_decode_parse_error(parse, "Unexpected end of data");
            case ','    :
                found_comma = 1;
                json_decode_parse_next_char(parse);
                break;
            case '}'    :
                done = 1;
                json_decode_parse_next_char(parse);
                break;
            case ' '    :
            case '\t'   :
            case '\r'   :
                json_decode_parse_next_char(parse);
                break;
#ifdef MUDOS
            case 0x0c   :
#else // MUDOS
            case '\f'   :
#endif // MUDOS
            case '\n'   :
                json_decode_parse_next_line(parse);
                break;
            default     :
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        }
        out[key] = value;
    }
    return out;
}

private mixed json_decode_parse_array(mixed array parse) {
    mixed array out = ({});
    int done = 0;
    int found_comma;
    json_decode_parse_next_char(parse);
    if(parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]] == ']') {
        done = 1;
        json_decode_parse_next_char(parse);
    }
    while(!done) {
        mixed value = json_decode_parse_value(parse);
        found_comma = 0;
        while(!found_comma && !done) {
            int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
            switch(ch) {
            case 0      :
                json_decode_parse_error(parse, "Unexpected end of data");
            case ','    :
                found_comma = 1;
                json_decode_parse_next_char(parse);
                break;
            case ']'    :
                done = 1;
                json_decode_parse_next_char(parse);
                break;
            case ' '    :
            case '\t'   :
            case '\r'   :
                json_decode_parse_next_char(parse);
                break;
#ifdef MUDOS
            case 0x0c   :
#else // MUDOS
            case '\f'   :
#endif // MUDOS
            case '\n'   :
                json_decode_parse_next_line(parse);
                break;
            default     :
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        }
        out += ({ value });
    }
    return out;
}

private varargs mixed json_decode_parse_string(mixed array parse, int initiator_checked) {
    int from, to, esc_state, esc_active;
    string out;
    if(!initiator_checked) {
        int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
        if(!ch)
            json_decode_parse_error(parse, "Unexpected end of data");
        if(ch != '"')
            json_decode_parse_error(parse, "Unexpected character", ch);
    }
    json_decode_parse_next_char(parse);
    from = parse[JSON_DECODE_PARSE_POS];
    to = -1;
    esc_state = 0;
    esc_active = 0;
    while(to == -1) {
        switch(parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]]) {
        case 0          :
            json_decode_parse_error(parse, "Unexpected end of data");
        case '\\'       :
            esc_state = !esc_state;
            break;
        case '"'        :
            if(esc_state) {
                esc_state = 0;
                esc_active++;
            } else {
                to = parse[JSON_DECODE_PARSE_POS] - 1;
            }
            break;
        default         :
            if(esc_state) {
                esc_state = 0;
                esc_active++;
            }
            break;
        }
        json_decode_parse_next_char(parse);
    }
    out = parse[JSON_DECODE_PARSE_TEXT][from .. to];
    if(esc_active) {
        if(member(out, '"') != -1)
            out = replace_string(out, "\\\"", "\"");
        if(strstr(out, "\\b") != -1)
            out = replace_string(out, "\\b", "\b");
#ifdef MUDOS
        if(strstr(out, "\\f") != -1)
            out = replace_string(out, "\\f", "\x0c");
#else // MUDOS
        if(strstr(out, "\\f") != -1)
            out = replace_string(out, "\\f", "\f");
#endif // MUDOS
        if(strstr(out, "\\n") != -1)
            out = replace_string(out, "\\n", "\n");
        if(strstr(out, "\\r") != -1)
            out = replace_string(out, "\\r", "\r");
        if(strstr(out, "\\t") != -1)
            out = replace_string(out, "\\t", "\t");
        if(strstr(out, "\\u") != -1) {
            string array parts = explode(out, "\\u");
            int entries = sizeof(parts) * 2 - 1;
            string array proc = allocate(entries);
            int i, j;
            proc[0] = parts[0];
            for(i = 1, j = sizeof(parts); i < j; i++) {
                string part = parts[i];
                int array nybbles = allocate(4);
                int array bytes = allocate(2);
                for(int k = 0; k < 4; k++)
                    if((nybbles[k] = json_decode_hexdigit(part[k])) == -1)
                        json_decode_parse_error(parse, "Invalid hex digit", part[k]);
                bytes[0] = (nybbles[0] << 4) | nybbles[1];
                bytes[1] = (nybbles[2] << 4) | nybbles[3];
                if(member(bytes, 0) != -1)
                    bytes -= ({ 0 });
#ifdef MUDOS
                proc[i * 2 - 1] = sprintf("%@c", bytes);
#else // MUDOS
                proc[i * 2 - 1] = to_string(bytes);
#endif // MUDOS
                proc[i * 2] = part[4..];
            }
            out = implode(proc, "");
        }
        if(member(out, '/') != -1)
            out = replace_string(out, "\\/", "/");
        if(member(out, '\\') != -1)
            out = replace_string(out, "\\\\", "\\");
    }
    return out;
}

private mixed json_decode_parse_number(mixed array parse) {
    int from = parse[JSON_DECODE_PARSE_POS];
    int to = -1;
    int dot = -1;
    int exp = -1;
    int first_ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
    int ch;
    string number;
    switch(first_ch) {
    case '-'            :
        {
            int next_ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS] + 1];
            if(next_ch != '0')
                break;
            json_decode_parse_next_char(parse);
        }
        // Fallthrough
    case '0'            :
        json_decode_parse_next_char(parse);
        ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
        if(ch) {
            if(ch != '.')
                json_decode_parse_error(parse, "Unexpected character", ch);
            dot = parse[JSON_DECODE_PARSE_POS];
        }
        break;
    }
    json_decode_parse_next_char(parse);
    while(to == -1) {
        ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
        switch(ch) {
        case '.'        :
            if(dot != -1 || exp != -1)
                json_decode_parse_error(parse, "Unexpected character", ch);
            dot = parse[JSON_DECODE_PARSE_POS];
            json_decode_parse_next_char(parse);
            break;
        case '0'        :
        case '1'        :
        case '2'        :
        case '3'        :
        case '4'        :
        case '5'        :
        case '6'        :
        case '7'        :
        case '8'        :
        case '9'        :
            json_decode_parse_next_char(parse);
            break;
        case 'e'        :
        case 'E'        :
            if(exp != -1)
                json_decode_parse_error(parse, "Unexpected character", ch);
            exp = parse[JSON_DECODE_PARSE_POS];
            json_decode_parse_next_char(parse);
            break;
        case '-'        :
        case '+'        :
            if(exp == parse[JSON_DECODE_PARSE_POS] - 1) {
                json_decode_parse_next_char(parse);
                break;
            }
            // Fallthrough
        default         :
            to = parse[JSON_DECODE_PARSE_POS] - 1;
            if(dot == to || to < from)
                json_decode_parse_error(parse, "Unexpected character", ch);
            break;
        }
    }
    number = parse[JSON_DECODE_PARSE_TEXT][from .. to];
    if(dot != -1 || exp != -1)
        return to_float(number);
    else
        return to_int(number);
}

private mixed json_decode_parse_value(mixed array parse) {
    for(;;) {
        int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
        switch(ch) {
        case 0          :
            json_decode_parse_error(parse, "Unexpected end of data");
        case '{'        :
            return json_decode_parse_object(parse);
        case '['        :
            return json_decode_parse_array(parse);
        case '"'        :
            return json_decode_parse_string(parse, 1);
        case '-'        :
        case '0'        :
        case '1'        :
        case '2'        :
        case '3'        :
        case '4'        :
        case '5'        :
        case '6'        :
        case '7'        :
        case '8'        :
        case '9'        :
            return json_decode_parse_number(parse);
        case ' '        :
        case '\t'       :
        case '\r'       :
            json_decode_parse_next_char(parse);
            break;
#ifdef MUDOS
        case 0x0c       :
#else // MUDOS
        case '\f'       :
#endif // MUDOS
        case '\n'       :
            json_decode_parse_next_line(parse);
            break;
        case 't'        :
            if(json_decode_parse_at_token(parse, "true", 1)) {
                json_decode_parse_next_chars(parse, 4);
                return 1;
            } else {
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        case 'f'        :
            if(json_decode_parse_at_token(parse, "false", 1)) {
                json_decode_parse_next_chars(parse, 5);
                return 0;
            } else {
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        case 'n'        :
            if(json_decode_parse_at_token(parse, "null", 1)) {
                json_decode_parse_next_chars(parse, 4);
                return 0;
            } else {
                json_decode_parse_error(parse, "Unexpected character", ch);
            }
        default         :
            json_decode_parse_error(parse, "Unexpected character", ch);
        }
    }
}

private mixed json_decode_parse(mixed array parse) {
    mixed out = json_decode_parse_value(parse);
    for(;;) {
        int ch = parse[JSON_DECODE_PARSE_TEXT][parse[JSON_DECODE_PARSE_POS]];
        switch(ch) {
        case 0          :
            return out;
        case ' '        :
        case '\t'       :
        case '\r'       :
            json_decode_parse_next_char(parse);
            break;
#ifdef MUDOS
        case 0x0c       :
#else // MUDOS
        case '\f'       :
#endif // MUDOS
        case '\n'       :
            json_decode_parse_next_line(parse);
            break;
        default         :
            json_decode_parse_error(parse, "Unexpected character", ch);
        }
    }
    return 0;
}

mixed json_decode(string text) {
    mixed array parse = allocate(JSON_DECODE_PARSE_FIELDS);
    parse[JSON_DECODE_PARSE_TEXT] = text;
    parse[JSON_DECODE_PARSE_POS] = 0;
    parse[JSON_DECODE_PARSE_CHAR] = 1;
    parse[JSON_DECODE_PARSE_LINE] = 1;
    return json_decode_parse(parse);
}

varargs string json_encode(mixed value, mixed array pointers) {
#ifdef MUDOS
    if(undefinedp(value))
        return "null";
    if(intp(value) || floatp(value))
#else // MUDOS
    switch(typeof(value)) {
    case T_NUMBER   :
    case T_FLOAT    :
#endif // MUDOS
        return to_string(value);
#ifdef MUDOS
    if(stringp(value)) {
#else // MUDOS
    case T_STRING   :
#endif // MUDOS
        if(member(value, '"') != -1)
            value = replace_string(value, "\"", "\\\"");
        value = sprintf("\"%s\"", value);
        if(member(value, '\\') != -1) {
            value = replace_string(value, "\\", "\\\\");
            if(strstr(value, "\\\"") != -1)
                value = replace_string(value, "\\\"", "\"");
        }
        if(member(value, '\b') != -1)
            value = replace_string(value, "\b", "\\b");
#ifdef MUDOS
        if(member(value, 0x0c) != -1)
            value = replace_string(value, "\x0c", "\\f");
#else // MUDOS
        if(member(value, '\f') != -1)
            value = replace_string(value, "\f", "\\f");
#endif // MUDOS
        if(member(value, '\n') != -1)
            value = replace_string(value, "\n", "\\n");
        if(member(value, '\r') != -1)
            value = replace_string(value, "\r", "\\r");
        if(member(value, '\t') != -1)
            value = replace_string(value, "\t", "\\t");
        return value;
#ifdef MUDOS
    }
    if(mapp(value)) {
        string out;
        int ix = 0;
        if(pointers) {
            // Don't recurse into circular data structures, output null for
            // their interior reference
            if(member(pointers, value) != -1)
                return "null";
            pointers += ({ value });
        } else {
            pointers = ({ value });
        }
        foreach(mixed k, mixed v in value) {
            // Non-string keys are skipped because the JSON spec requires that
            // object field names be strings.
            if(!stringp(k))
                continue;
            if(ix++)
                out = sprintf("%s,%s:%s", out, json_encode(k, pointers), json_encode(v, pointers));
            else
                out = sprintf("%s:%s", json_encode(k, pointers), json_encode(v, pointers));
        }
        return sprintf("{%s}", out);
    }
#else // MUDOS
    case T_MAPPING  :
        {
#ifdef __LDMUD__
            int width = widthof(value);
#else // __LDMUD__
            int width = get_type_info(value, 1);
#endif // __LDMUD__
            switch(width) {
            case 1      :
                {
                    string out;
                    int ix = 0;
                    if(pointers) {
                        // Don't recurse into circular data structures, output null for
                        // their interior reference
                        if(member(pointers, value) != -1)
                            return "null";
                        pointers += ({ value });
                    } else {
                        pointers = ({ value });
                    }
                    foreach(mixed k, mixed v : value) {
                        // Non-string keys are skipped because the JSON spec requires that
                        // object field names be strings.
                        if(!stringp(k))
                            continue;
                        if(ix++)
                            out = sprintf("%s,%s:%s", out, json_encode(k, pointers), json_encode(v, pointers));
                        else
                            out = sprintf("%s:%s", json_encode(k, pointers), json_encode(v, pointers));
                    }
                    return sprintf("{%s}", out);
                }
            default     :
                // Multivalue mappings are represented using arrays for the value sets.
                // This isn't a reversible representation, so it's fairly problematic, but
                // it seems marginally better than just dropping the values on the floor.
                {
                    mixed array values;
                    string out;
                    int ix = 0;
                    if(pointers) {
                        // Don't recurse into circular data structures, output null for
                        // their interior reference
                        if(member(pointers, value) != -1)
                            return "null";
                        pointers += ({ value });
                    } else {
                        pointers = ({ value });
                    }
                    values = allocate(width);
                    foreach(mixed k, mixed v1, mixed v2 : value) {
                        // Non-string keys are skipped because the JSON spec requires that
                        // object field names be strings.
                        if(!stringp(k))
                            continue;
                        values[0] = v1;
                        values[1] = v2;
                        for(int w = 2; w < width; w++)
                            values[w] = value[k, w];
                        if(ix++)
                            out = sprintf("%s,%s:%s", out, json_encode(k, pointers), json_encode(values, pointers));
                        else
                            out = sprintf("%s:%s", json_encode(k, pointers), json_encode(values, pointers));
                    }
                    return sprintf("{%s}", out);
                }
            case 0      :
                break;
            }
        }
#endif // MUDOS
#ifdef MUDOS
    if(arrayp(value))
#else // MUDOS
    case T_POINTER  :
#endif // MUDOS
        {
            string out;
            int ix = 0;
            if(pointers) {
                // Don't recurse into circular data structures, output null for
                // their interior reference
                if(member(pointers, value) != -1)
                    return "null";
                pointers += ({ value });
            } else {
                pointers = ({ value });
            }
            foreach(mixed v in value)
                if(ix++)
                    out = sprintf("%s,%s", out, json_encode(v, pointers));
                else
                    out = json_encode(v, pointers);
            return sprintf("[%s]", out);
        }
#ifndef MUDOS
    }
#endif // !MUDOS
    // Values that cannot be represented in JSON are replaced by nulls.
    return "null";
}
 

Source File: json-test.c

// The below is a test set for the Lost Souls (http://lostsouls.org/) unit
// testing rig.  It won't be usable directly outside of that system, but is
// provided as an example in principle of testing this module.

#include <test.h>

inherit "/mod/algorithm/json";
inherit "/std/test";

void test_simple_mapping() {
    mapping value = ([
        "test"              : 65536,
    ]);
    string expect = "{\"test\":65536}";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
    mixed rev = json_decode(fwd);
    AssertEquivalent(value, rev);
}

void test_simple_array() {
    mixed array value = ({ 100, "test", "another", -85 });
    string expect = "[100,\"test\",\"another\",-85]";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
    mixed rev = json_decode(fwd);
    AssertEquivalent(value, rev);
}

void test_complex_structure() {
    mapping value = ([
        "test"              : 65536,
        "more"              : ([
            "data"          : ({ 1, 27, -0.7, ({ 8, "embedded" })}),
            "also"          : ({
                "item with\nnewlines\nand\ttabs\r\nand a CRLF in it",
                ([
                     "thing" : "other thing",
                ]),
            }),
        ]),
    ]);
    string fwd = json_encode(value);
    mixed rev = json_decode(fwd);
    AssertEquivalent(value, rev);
}

void test_nonnative_json() {
    string json;
    mixed expect;
    mixed proc;
    json = " { \"user\" :\t{ \"name\": \"Bob\" , \"validated\":true,\"profiles\":[{\"url\":\"http://facespace.com/bob\",\"useful\":false},\n{\"url\":\"http://bugroff.com/bob\",\"useful\":null}\n],\n\"sequence\":[4,8,15,16,23,42]},\r\n\t\"login\":\"2014-10-01 18:35:12\"}\n ";
    expect = ([
        "user"                  : ([
            "name"              : "Bob",
            "validated"         : 1,
            "profiles"          : ({
                ([
                    "url"       : "http://facespace.com/bob",
                    "useful"    : 0,
                ]),
                ([
                    "url"       : "http://bugroff.com/bob",
                    "useful"    : 0,
                ]),
            }),
            "sequence"          : ({ 4, 8, 15, 16, 23, 42 }),
        ]),
        "login"                 : "2014-10-01 18:35:12",
    ]);
    proc = json_decode(json);
    AssertEquivalent(expect, proc);
    json = " [ \n8e-3, 7.5E6,  -2e-2 \t]";
    expect = ({ 0.008, 7500000, -0.02 });
    proc = json_decode(json);
    AssertEquivalent(expect, proc);
}

void test_floats() {
    float array values = ({ 0.5, 27.3, 0.001, 0.000001, 1000000.0, 0.0000000000002, 2000000000000.0 });
    foreach(float value : values) {
        string pos_fwd = json_encode(value);
        mixed pos_rev = json_decode(pos_fwd);
        AssertEquivalent(value, pos_rev);
        float neg_value = -value;
        string neg_fwd = json_encode(neg_value);
        mixed neg_rev = json_decode(neg_fwd);
        AssertEquivalent(neg_value, neg_rev);
    }
    for(int ix = 0; ix < 200000; ix += ((ix ^ 0xdead) & 0xff) + 1) {
        float value = to_float(ix);
        string pos_fwd = json_encode(value);
        mixed pos_rev = json_decode(pos_fwd);
        AssertEquivalent(value, pos_rev);
        float neg_value = -value;
        string neg_fwd = json_encode(neg_value);
        mixed neg_rev = json_decode(neg_fwd);
        AssertEquivalent(neg_value, neg_rev);
    }
}

void test_ints() {
    int array values = ({ 0, 1, 27, 1000, 27638, 8381237, __INT_MAX__ - 1 });
    foreach(int value : values) {
        string pos_fwd = json_encode(value);
        mixed pos_rev = json_decode(pos_fwd);
        AssertEquivalent(value, pos_rev);
        int neg_value = -value;
        string neg_fwd = json_encode(neg_value);
        mixed neg_rev = json_decode(neg_fwd);
        AssertEquivalent(neg_value, neg_rev);
    }
    for(int value = 0; value < 200000; value += ((value ^ 0xdead) & 0xff) + 1) {
        string pos_fwd = json_encode(value);
        mixed pos_rev = json_decode(pos_fwd);
        AssertEquivalent(value, pos_rev);
        int neg_value = -value;
        string neg_fwd = json_encode(neg_value);
        mixed neg_rev = json_decode(neg_fwd);
        AssertEquivalent(neg_value, neg_rev);
    }
}

void test_object() {
    mixed value = this_object();
    string expect = "null";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
}

void test_closure() {
    mixed value = #'test_closure;
    string expect = "null";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
}

void test_quoted_array() {
    mixed value = '
({ 273 });
    string expect = "null";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
}

void test_symbol() {
    mixed value = 'test;
    string expect = "null";
    string fwd = json_encode(value);
    AssertEquals(expect, fwd);
}

© 2008-2012 Lost Souls, a free text-based RPG
processing time: 0.226s