Annotation of parser3/src/classes/json.C, revision 1.72

1.1       misha       1: /** @file
                      2:        Parser: @b json parser class.
                      3: 
1.70      moko        4:        Copyright (c) 2000-2026 Art. Lebedev Studio (https://www.artlebedev.com)
1.57      moko        5:        Authors: Konstantin Morshnev <moko@design.ru>
1.1       misha       6: */
                      7: 
                      8: #include "classes.h"
                      9: #include "pa_vmethod_frame.h"
                     10: 
                     11: #include "pa_request.h"
                     12: #include "pa_vbool.h"
1.60      moko       13: #include "pa_varray.h"
1.1       misha      14: 
                     15: #include "pa_charset.h"
                     16: #include "pa_charsets.h"
1.29      moko       17: #include "pa_json.h"
1.1       misha      18: 
1.14      misha      19: #ifdef XML
                     20: #include "pa_vxdoc.h"
                     21: #endif
                     22: 
1.72    ! moko       23: volatile const char * IDENT_JSON_C="$Id: json.C,v 1.71 2026/10/13 20:02:00 moko Exp $";
1.17      moko       24: 
1.1       misha      25: // class
                     26: 
                     27: class MJson: public Methoded {
                     28: public:
                     29:        MJson();
1.64      moko       30: 
                     31:        override Value* get_element(const String&);
                     32:        override const VJunction* put_element(const String&, Value*);
1.1       misha      33: };
                     34: 
                     35: // global variable
                     36: 
1.41      moko       37: DECLARE_CLASS_VAR(json, new MJson);
1.1       misha      38: 
1.72    ! moko       39: THREAD_LOCAL bool handle_array_default=true; // thread local: can be changed anytime
1.64      moko       40: 
1.1       misha      41: // methods
1.53      moko       42: struct Json : public PA_Allocated {
1.59      moko       43:        Stack<VHashBase*> stack;
1.3       moko       44:        Stack<String*> key_stack;
1.1       misha      45: 
1.3       moko       46:        String* key;
1.1       misha      47:        Value* result;
                     48: 
1.16      misha      49:        Junction* hook_object;
                     50:        Junction* hook_array;
1.3       moko       51:        Request* request;
                     52: 
1.1       misha      53:        Charset *charset;
1.23      moko       54:        String::Language taint;
                     55: 
1.1       misha      56:        bool handle_double;
1.30      misha      57:        bool handle_int;
1.59      moko       58:        bool handle_array;
1.4       moko       59:        enum Distinct { D_EXCEPTION, D_FIRST, D_LAST, D_ALL } distinct;
1.3       moko       60: 
1.23      moko       61:        Json(Charset* acharset): stack(), key_stack(), key(NULL), result(NULL), hook_object(NULL), hook_array(NULL), 
1.30      misha      62:                request(NULL), charset(acharset), taint(String::L_TAINTED), handle_double(true), handle_int(true), 
1.64      moko       63:                handle_array(handle_array_default), distinct(D_EXCEPTION){}
1.4       moko       64: 
                     65:        bool set_distinct(const String &value){
                     66:                if (value == "first") distinct = D_FIRST;
                     67:                else if (value == "last") distinct = D_LAST;
                     68:                else if (value == "all") distinct = D_ALL;
                     69:                else return false;
                     70:                return true;
                     71:        }
1.59      moko       72: 
                     73:        bool set_handle_array(const String &value){
                     74:                if (value == "array") handle_array = true;
                     75:                else if (value == "hash") handle_array = false;
                     76:                else return false;
                     77:                return true;
                     78:        }
1.1       misha      79: };
                     80: 
1.64      moko       81: 
                     82: Value* MJson::get_element(const String& aname) {
                     83:        if (aname=="array"){
                     84:                return new VString(*new String(handle_array_default ? "array" : "hash"));
                     85:        }
                     86:        return Methoded::get_element(aname);
                     87: }
                     88: 
                     89: const VJunction* MJson::put_element(const String& aname, Value* avalue) {
                     90:        if (aname=="array"){
                     91:                Json json(NULL);
                     92:                if (avalue->get_string()){
                     93:                        const String& sarray=avalue->as_string();
                     94:                        if (json.set_handle_array(sarray)){
                     95:                                handle_array_default=json.handle_array;
                     96:                                return 0;
                     97:                        }
                     98:                        throw Exception(PARSER_RUNTIME, &sarray, "$json:array must be 'array' or 'hash'");
                     99:                }
                    100:                throw Exception(PARSER_RUNTIME, 0, "$json:array must be 'array' or 'hash'");
                    101:        }
                    102:        return Methoded::put_element(aname, avalue);
                    103: }
                    104: 
                    105: 
1.72    ! moko      106: static String numbered_key(const String& key, uint number){
        !           107:        String result;
        !           108:        result << key << "_" << pa_uitoa(number);
        !           109:        return result;
        !           110: }
        !           111: 
1.1       misha     112: static void set_json_value(Json *json, Value *value){
1.59      moko      113:        VHashBase *top = json->stack.top_value();
1.3       moko      114:        if(json->key == NULL){
1.59      moko      115:                top->add(value);
1.1       misha     116:        } else {
1.4       moko      117:                switch (json->distinct){
                    118:                        case Json::D_EXCEPTION:
                    119:                                if (top->hash().put_dont_replace(*json->key, value))
                    120:                                        throw Exception(PARSER_RUNTIME, json->key, "duplicate key");
                    121:                                break;
                    122:                        case Json::D_FIRST:
                    123:                                top->hash().put_dont_replace(*json->key, value);
                    124:                                break;
                    125:                        case Json::D_LAST:
                    126:                                top->hash().put(*json->key, value);
                    127:                                break;
                    128:                        case Json::D_ALL:
                    129:                                if (top->hash().put_dont_replace(*json->key, value)){
1.72    ! moko      130:                                        // linear counting every time takes quadratic time, thus binary search; one of 2..count+1 is free for sure
        !           131:                                        uint lo=2, hi=top->hash().count()+1;
        !           132:                                        while(lo<hi){
        !           133:                                                uint mid=lo+(hi-lo)/2;
        !           134:                                                if(top->hash().get(numbered_key(*json->key, mid)))
        !           135:                                                        lo=mid+1;
        !           136:                                                else
        !           137:                                                        hi=mid;
1.4       moko      138:                                        }
1.72    ! moko      139:                                        // the found number is free, unless the numbered keys have gaps
        !           140:                                        for(uint i=lo;;i++)
        !           141:                                                if (!top->hash().put_dont_replace(numbered_key(*json->key, i), value)) break;
1.4       moko      142:                                }
                    143:                                break;
                    144:                }
1.3       moko      145:                json->key=NULL;
1.1       misha     146:        }
                    147: }
                    148: 
1.69      moko      149: String* json_string(Json *json, const char *value){
                    150:        /* do_callback_withbuf guarantees a null-terminated value */
                    151:        if (size_t length = strlen(value)){
                    152:                String::C result = json->charset !=NULL ?
                    153:                        Charset::transcode(String::C(value, length), pa_UTF8_charset, *json->charset) :
                    154:                        String::C(pa_strdup(value, length), length);
                    155:                return new String(result, json->taint);
                    156:        }
                    157:        return (String*)&String::Empty;
1.1       misha     158: }
                    159: 
1.3       moko      160: static Value *json_hook(Request &r, Junction *hook, String* key, Value* value){
1.10      moko      161:        Value *params[]={new VString(key ? *key : String::Empty), value};
1.49      moko      162:        METHOD_FRAME_ACTION(*hook->method, r.method_frame, hook->self, {
                    163:                frame.store_params(params, 2);
                    164:                r.call(frame);
                    165:                return &frame.result();
                    166:        });
1.1       misha     167: }
                    168: 
1.69      moko      169: static int json_callback(Json *json, int type, const char *value, uint32_t)
1.1       misha     170: {
                    171:        switch(type) {
1.25      moko      172:                case JSON_OBJECT_BEGIN:{
1.4       moko      173:                        VHash *v = new VHash();
1.16      misha     174:                        if (json->hook_object){
1.1       misha     175:                                json->key_stack.push(json->key);
1.16      misha     176:                                json->key=NULL;
1.1       misha     177:                        } else {
                    178:                                if (json->stack.count()) set_json_value(json, v);
                    179:                        }
                    180:                        json->stack.push(v);
                    181:                        break;
                    182:                }
1.25      moko      183:                case JSON_OBJECT_END:{
1.16      misha     184:                        if (json->hook_object){
1.3       moko      185:                                String* key = json->key_stack.pop();
1.16      misha     186:                                json->result = json_hook(*json->request, json->hook_object, key, json->stack.pop());
1.1       misha     187:                                
                    188:                                if (json->stack.count()){
                    189:                                        json->key = key;
                    190:                                        set_json_value(json, json->result);
                    191:                                }
                    192:                        } else {
                    193:                                json->result = json->stack.pop();
                    194:                        }
                    195:                        break;
                    196:                }
1.25      moko      197:                case JSON_ARRAY_BEGIN:{
1.16      misha     198:                        if (json->hook_array){
                    199:                                json->key_stack.push(json->key);
                    200:                                json->key=NULL;
1.59      moko      201:                                json->stack.push(new VHash);
1.16      misha     202:                        } else {
1.60      moko      203:                                VHashBase *v = json->handle_array ? (VHashBase *)new VArray : (VHashBase *)new VHash;
1.16      misha     204:                                if (json->stack.count()) set_json_value(json, v);
1.59      moko      205:                                json->stack.push(v);
1.16      misha     206:                        }
1.1       misha     207:                        break;
                    208:                }
1.25      moko      209:                case JSON_ARRAY_END:
1.12      moko      210:                        // libjson supports array at top level, we too
1.16      misha     211:                        if (json->hook_array){
                    212:                                String* key = json->key_stack.pop();
                    213:                                json->result = json_hook(*json->request, json->hook_array, key, json->stack.pop());
                    214:                                
                    215:                                if (json->stack.count()){
                    216:                                        json->key = key;
                    217:                                        set_json_value(json, json->result);
                    218:                                }
                    219:                        } else {
                    220:                                json->result = json->stack.pop();
                    221:                        }
1.1       misha     222:                        break;
1.25      moko      223:                case JSON_KEY:
1.69      moko      224:                        json->key = json_string(json, value);
1.16      misha     225:                        break;
1.25      moko      226:                case JSON_INT:
1.30      misha     227:                        if (json->handle_int){
1.69      moko      228:                                set_json_value(json, new VDouble( json_string(json, value)->as_double() ));
1.30      misha     229:                        } else {
                    230:                                // JSON_STRING
1.69      moko      231:                                set_json_value(json, new VString(*json_string(json, value)));
1.30      misha     232:                        }
1.1       misha     233:                        break;
1.25      moko      234:                case JSON_FLOAT:
1.1       misha     235:                        if (json->handle_double){
1.69      moko      236:                                set_json_value(json, new VDouble( json_string(json, value)->as_double() ));
1.1       misha     237:                                break;
1.25      moko      238:                        } // else is JSON_STRING
                    239:                case JSON_STRING:
1.69      moko      240:                        set_json_value(json, new VString(*json_string(json, value)));
1.1       misha     241:                        break;
1.25      moko      242:                case JSON_NULL:
1.18      moko      243:                        set_json_value(json, VVoid::get());
1.1       misha     244:                        break;
1.25      moko      245:                case JSON_TRUE:
1.1       misha     246:                        set_json_value(json, &VBool::get(true));
                    247:                        break;
1.25      moko      248:                case JSON_FALSE:
1.1       misha     249:                        set_json_value(json, &VBool::get(false));
1.25      moko      250:                        break;
1.1       misha     251:        }
1.25      moko      252:        return 0;
1.1       misha     253: }
                    254: 
1.5       moko      255: static const char* json_error_message(int error_code){
                    256:        static const char* error_messages[] = {
1.1       misha     257:                NULL,
1.25      moko      258:                "out of memory",
                    259:                "bad character",
                    260:                "stack empty",
                    261:                "pop unexpected mode",
                    262:                "nesting limit",
                    263:                "data limit",
                    264:                "comment not allowed by config",
1.35      moko      265:                "unexpected character",
1.25      moko      266:                "missing unicode low surrogate",
                    267:                "unexpected unicode low surrogate",
                    268:                "error comma out of structure",
                    269:                "error in a callback"
1.1       misha     270:        };
                    271:        return error_messages[error_code];
                    272: }
                    273: 
1.35      moko      274: #define SOURCE_MAX_LEN 60
                    275: 
                    276: void json_exception_with_source(Request& r, const char* msg, const char* json, int offset){
                    277:        int i;
                    278: 
                    279:        int line=0;
                    280:        int start=0;
                    281:        int end=strlen(json);
                    282: 
                    283:        if(offset>end)
                    284:                offset=end;
                    285: 
                    286:        for(i = 0; i < offset; i++){
                    287:                if(json[i]=='\n'){
                    288:                        line++;
                    289:                }
                    290:        }
                    291: 
                    292:        if(offset > SOURCE_MAX_LEN/2)
                    293:                start = offset - SOURCE_MAX_LEN/2;
                    294: 
                    295:        for(i = offset-1; i>=start; i--){
                    296:                if(json[i]=='\n'){
                    297:                        start=i+1;
                    298:                        break;
                    299:                }
                    300:        }
                    301: 
                    302:        if(start+SOURCE_MAX_LEN < end)
                    303:                end=start+SOURCE_MAX_LEN;
                    304: 
                    305:        for(i = offset+1; i<end; i++){
                    306:                if(json[i]=='\n'){
                    307:                        end=i;
                    308:                        break;
                    309:                }
                    310:        }
                    311: 
                    312:        char *source = pa_strdup(json+start, end-start);
                    313:        int source_offset = offset-start;
                    314: 
                    315:        if(source[source_offset]=='\n')
                    316:                source[source_offset]=' ';
                    317: 
                    318:        for(i = 0; i < source_offset; i++){
                    319:                if(source[i]=='\t'){
                    320:                        source[i]=' ';
                    321:                }
                    322:        }
                    323: 
                    324:        if(r.charsets.source().isUTF8()){
                    325:                source=(char *)fixUTF8(source);
                    326:                if(source_offset>0){
                    327:                        String s_source(pa_strdup(source,source_offset));
                    328:                        source_offset=s_source.length(r.charsets.source());
                    329:                }
                    330:        }
                    331: 
                    332:        throw Exception("json.parse", 0, "%s at line %d\n%s\n%*s", msg, line+1, source, source_offset+1, "^");
                    333: }
                    334: 
1.1       misha     335: static void _parse(Request& r, MethodParams& params) {
1.3       moko      336:        const String& json_string=params.as_string(0, "json must be string");
                    337: 
                    338:        Json json(r.charsets.source().isUTF8() ? NULL : &(r.charsets.source()));
1.1       misha     339: 
1.25      moko      340:        json_config config = {
                    341:                0,              // buffer_initial_size
1.26      moko      342:                128,            // max_nesting
1.25      moko      343:                0,              // max_data
                    344:                1,              // allow_c_comments
                    345:                1,              // allow_yaml_comments
                    346:                pa_malloc,
                    347:                pa_realloc,
                    348:                pa_free
                    349:        };
1.1       misha     350: 
                    351:        if(params.count() == 2)
                    352:                if(HashStringValue* options=params.as_hash(1)) {
                    353:                        int valid_options=0;
                    354:                        if(Value* value=options->get("depth")) {
1.72    ! moko      355:                                int depth=r.process(*value).as_int();
        !           356:                                config.max_nesting=depth>0 ? depth : 0; // 0 is no limit, negative is the same
1.1       misha     357:                                valid_options++;
                    358:                        }
                    359:                        if(Value* value=options->get("double")) {
1.46      moko      360:                                json.handle_double=r.process(*value).as_bool();
1.4       moko      361:                                valid_options++;
                    362:                        }
1.30      misha     363:                        if(Value* value=options->get("int")) {
1.46      moko      364:                                json.handle_int=r.process(*value).as_bool();
1.30      misha     365:                                valid_options++;
                    366:                        }
1.4       moko      367:                        if(Value* value=options->get("distinct")) {
                    368:                                const String& sdistinct=value->as_string();
                    369:                                if (!json.set_distinct(sdistinct))
                    370:                                        throw Exception(PARSER_RUNTIME, &sdistinct, "must be 'first', 'last' or 'all'");
1.1       misha     371:                                valid_options++;
                    372:                        }
1.23      moko      373:                        if(Value* value=options->get("taint")) {
1.71      moko      374:                                json.taint=String::name2lang(value->as_string());
1.23      moko      375:                                valid_options++;
                    376:                        }
1.1       misha     377:                        if(Value* value=options->get("object")) {
1.16      misha     378:                                json.hook_object=value->get_junction();
1.3       moko      379:                                json.request=&r;
1.49      moko      380:                                if (!json.hook_object || !json.hook_object->method || !json.hook_object->method->params_names || !(json.hook_object->method->params_count == 2))
1.1       misha     381:                                        throw Exception(PARSER_RUNTIME, 0, "$.object must be parser method with 2 parameters");
                    382:                                valid_options++;
                    383:                        }
1.16      misha     384:                        if(Value* value=options->get("array")) {
1.59      moko      385:                                if(value->get_string()){
                    386:                                        const String& sarray=value->as_string();
                    387:                                        if (!json.set_handle_array(sarray))
                    388:                                                throw Exception(PARSER_RUNTIME, &sarray, "$.array must be parser method with 2 parameters or 'array' or 'hash'");
                    389:                                } else {
                    390:                                        json.hook_array=value->get_junction();
                    391:                                        json.request=&r;
                    392:                                        if (!json.hook_array || !json.hook_array->method || !json.hook_array->method->params_names || !(json.hook_array->method->params_count == 2))
                    393:                                                throw Exception(PARSER_RUNTIME, 0, "$.array must be parser method with 2 parameters or 'array' or 'hash'");
                    394:                                }
1.16      misha     395:                                valid_options++;
                    396:                        }
1.1       misha     397:                        if(valid_options!=options->count())
                    398:                                throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
                    399:                }
                    400: 
1.28      moko      401:        const String::Body json_body = json_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.44      moko      402:        const char *json_cstr = json.charset != NULL ? Charset::transcode(json_body, *json.charset, pa_UTF8_charset).cstr() : json_body.cstr();
1.1       misha     403: 
1.25      moko      404:        json_parser parser;
                    405:        if(int result = json_parser_init(&parser, &config, (json_parser_callback)&json_callback, &json))
                    406:                throw Exception("json.parse", 0, "%s", json_error_message(result));
                    407: 
1.36      moko      408:        if(!*json_cstr)
                    409:                throw Exception("json.parse", 0, "empty string is not valid json");
                    410: 
                    411:        const char *first_quote=strchr(json_cstr,'"');
                    412:        if(first_quote && first_quote>json_cstr && *(--first_quote) == '\\')
                    413:                json_exception_with_source(r, "illegal quote escape, json may be tainted", json_cstr, first_quote-json_cstr);
                    414: 
1.25      moko      415:        uint32_t processed;
                    416:        if(int result = json_parser_string(&parser, json_cstr, strlen(json_cstr), &processed))
1.35      moko      417:                json_exception_with_source(r, json_error_message(result), json_cstr, processed);
1.3       moko      418: 
1.25      moko      419:        if (!json_parser_is_done(&parser))
1.35      moko      420:                json_exception_with_source(r, "unexpected end of json data", json_cstr, processed);
                    421: 
1.25      moko      422:        json_parser_free(&parser);
1.1       misha     423: 
1.48      moko      424:        if (json.result) r.write(*json.result);
1.1       misha     425: }
                    426: 
1.63      moko      427: const uint ANTI_ENDLESS_JSON_STRING_RECURSION=128;
1.26      moko      428: 
1.8       moko      429: char *get_indent(uint level){
1.63      moko      430:        static char* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={};
1.8       moko      431:        if (!cache[level]){
1.56      moko      432:                char *result = static_cast<char*>(pa_malloc_atomic(level+1));
1.8       moko      433:                memset(result, '\t', level);
1.9       moko      434:                result[level]='\0';
1.8       moko      435:                return cache[level]=result;
                    436:        }
                    437:        return cache[level];
                    438: }
                    439: 
1.56      moko      440: String *get_delim(uint level){
1.63      moko      441:        static String* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={};
1.56      moko      442: 
                    443:        if (!cache[level]){
                    444:                char *result = static_cast<char*>(pa_malloc_atomic(level+2+1+1));
                    445:                result[0]=',';
                    446:                result[1]='\n';
                    447:                memset(result+2, '\t', level);
                    448:                result[level+2]='"';
                    449:                result[level+3]='\0';
                    450:                return cache[level] = new String(result, String::L_AS_IS);
                    451:        }
                    452:        return cache[level];
                    453: }
                    454: 
1.59      moko      455: String *get_array_delim(uint level){
1.63      moko      456:        static String* cache[ANTI_ENDLESS_JSON_STRING_RECURSION]={};
1.59      moko      457: 
                    458:        if (!cache[level]){
                    459:                char *result = static_cast<char*>(pa_malloc_atomic(level+2+1));
                    460:                result[0]=',';
                    461:                result[1]='\n';
                    462:                memset(result+2, '\t', level);
                    463:                result[level+2]='\0';
                    464:                return cache[level] = new String(result, String::L_AS_IS);
                    465:        }
                    466:        return cache[level];
                    467: }
                    468: 
1.63      moko      469: class Json_string_recursion {
1.26      moko      470:        Json_options& foptions;
                    471: public:
1.63      moko      472:        Json_string_recursion(Json_options& aoptions) : foptions(aoptions) {
1.72    ! moko      473:                if(++foptions.json_string_recursion>=ANTI_ENDLESS_JSON_STRING_RECURSION)
1.26      moko      474:                        throw Exception(PARSER_RUNTIME, 0, "call canceled - endless json recursion detected");
                    475:        }
1.63      moko      476:        ~Json_string_recursion() {
                    477:                if(foptions.json_string_recursion)
                    478:                        foptions.json_string_recursion--;
1.26      moko      479:        }
                    480: };
                    481: 
1.21      moko      482: const String& value_json_string(String::Body key, Value& v, Json_options& options);
1.6       misha     483: 
1.37      moko      484: const String* Json_options::hash_json_string(HashStringValue *hash) {
                    485:        if(!hash || !hash->count())
1.21      moko      486:                return new String("{}", String::L_AS_IS);
1.8       moko      487: 
1.63      moko      488:        Json_string_recursion go_down(*this);
1.8       moko      489: 
                    490:        String& result = *new String("{\n", String::L_AS_IS);
                    491: 
1.21      moko      492:        if (indent){
1.8       moko      493: 
                    494:                String *delim=NULL;
1.63      moko      495:                indent=get_indent(json_string_recursion);
1.37      moko      496:                for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8       moko      497:                        if (delim){
                    498:                                result << *delim;
                    499:                        } else {
1.21      moko      500:                                result << indent << "\"";
1.63      moko      501:                                delim = get_delim(json_string_recursion);
1.8       moko      502:                        }
1.21      moko      503:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      504:                }
1.63      moko      505:                result << "\n" << (indent=get_indent(json_string_recursion-1)) << "}";
1.6       misha     506: 
1.8       moko      507:        } else {
                    508: 
                    509:                bool need_delim=false;
1.37      moko      510:                for(HashStringValue::Iterator i(*hash); i; i.next() ){
1.8       moko      511:                        result << (need_delim ? ",\n\"" : "\"");
1.21      moko      512:                        result << String(i.key(), String::L_JSON) << "\":" << value_json_string(i.key(), *i.value(), *this);
1.8       moko      513:                        need_delim=true;
                    514:                }
                    515:                result << "\n}";
1.6       misha     516: 
                    517:        }
                    518: 
1.21      moko      519:        return &result;
1.6       misha     520: }
                    521: 
1.59      moko      522: const String* Json_options::array_json_string(ArrayValue *array) {
                    523:        if(!array || !array->count())
                    524:                return new String("[]", String::L_AS_IS);
                    525: 
1.63      moko      526:        Json_string_recursion go_down(*this);
1.59      moko      527: 
                    528:        String& result = *new String("[\n", String::L_AS_IS);
                    529: 
                    530:        if (indent){
                    531: 
                    532:                String *delim=NULL;
1.63      moko      533:                indent=get_indent(json_string_recursion);
1.59      moko      534:                for(ArrayValue::Iterator i(*array); i; i.next() ){
                    535:                        if (delim){
                    536:                                result << *delim;
                    537:                        } else {
                    538:                                result << indent;
1.63      moko      539:                                delim = get_array_delim(json_string_recursion);
1.59      moko      540:                        }
1.67      moko      541:                        result << value_json_string(String::Body::uitoa(i.index()), i.value() ? *i.value() : static_cast<Value&>(*VVoid::get()), *this);
1.59      moko      542:                }
1.63      moko      543:                result << "\n" << (indent=get_indent(json_string_recursion-1)) << "]";
1.59      moko      544: 
                    545:        } else {
                    546: 
                    547:                bool need_delim=false;
                    548:                for(ArrayValue::Iterator i(*array); i; i.next() ){
                    549:                        if(need_delim) result << ",\n";
1.67      moko      550:                        result << value_json_string(String::Body::uitoa(i.index()), i.value() ? *i.value() : static_cast<Value&>(*VVoid::get()), *this);
1.59      moko      551:                        need_delim=true;
                    552:                }
                    553:                result << "\n]";
                    554: 
                    555:        }
                    556: 
                    557:        return &result;
                    558: }
                    559: 
                    560: const String* Json_options::array_compact_json_string(ArrayValue *array) {
                    561:        if(!array || !array->count())
                    562:                return new String("[]", String::L_AS_IS);
                    563: 
1.63      moko      564:        Json_string_recursion go_down(*this);
1.59      moko      565: 
                    566:        String& result = *new String("[\n", String::L_AS_IS);
                    567: 
                    568:        if (indent){
                    569: 
                    570:                String *delim=NULL;
1.63      moko      571:                indent=get_indent(json_string_recursion);
1.59      moko      572:                for(ArrayValue::Iterator i(*array); i; i.next() ){
                    573:                        if (i.value()){
                    574:                                if (delim){
                    575:                                        result << *delim;
                    576:                                } else {
                    577:                                        result << indent;
1.63      moko      578:                                        delim = get_array_delim(json_string_recursion);
1.59      moko      579:                                }
1.67      moko      580:                                result << value_json_string(String::Body::uitoa(i.index()), *i.value(), *this);
1.59      moko      581:                        }
                    582:                }
1.63      moko      583:                result << "\n" << (indent=get_indent(json_string_recursion-1)) << "]";
1.59      moko      584: 
                    585:        } else {
                    586: 
                    587:                bool need_delim=false;
                    588:                for(ArrayValue::Iterator i(*array); i; i.next() ){
                    589:                        if (i.value()){
                    590:                                if(need_delim) result << ",\n";
1.67      moko      591:                                result << value_json_string(String::Body::uitoa(i.index()), *i.value(), *this);
1.59      moko      592:                                need_delim=true;
                    593:                        }
                    594:                }
                    595:                result << "\n]";
                    596: 
                    597:        }
                    598: 
                    599:        return &result;
                    600: }
                    601: 
1.21      moko      602: static bool based_on(HashStringValue::key_type key, HashStringValue::value_type /*value*/, Value* v) {
1.15      misha     603:        return v->is(key.cstr());
                    604: }
1.26      moko      605: 
1.21      moko      606: const String& value_json_string(String::Body key, Value& v, Json_options& options) {
                    607:        if(options.methods) {
                    608:                Value* method=options.methods->get(v.type());
                    609:                if(!method){
                    610:                        method=options.methods->first_that<Value*>(based_on, &v);
1.31      misha     611:                        options.methods->put(v.type(), method ? method : VVoid::get());
1.21      moko      612:                }
                    613:                if(method && !method->is_void()) {
1.67      moko      614:                        static const String::Body sindent("indent");
1.6       misha     615:                        Junction* junction=method->get_junction();
1.26      moko      616:                        HashStringValue* params_hash=options.params && options.indent ? options.params->get_hash() : NULL;
1.67      moko      617:                        Temp_hash_value<HashStringValue, Value*> indent(params_hash, sindent, new VString(*new String(options.indent, String::L_AS_IS)));
1.26      moko      618: 
1.21      moko      619:                        Value *params[]={new VString(*new String(key, String::L_JSON)), &v, options.params ? options.params : VVoid::get()};
1.6       misha     620: 
1.49      moko      621:                        METHOD_FRAME_ACTION(*junction->method, options.r->method_frame, junction->self, {
                    622:                                frame.store_params(params, 3);
                    623:                                options.r->call(frame);
                    624:                                return frame.result().as_string();
                    625:                        });
1.6       misha     626:                }
1.15      misha     627:        }
1.6       misha     628: 
1.21      moko      629:        options.key=key;
1.6       misha     630:        return *v.get_json_string(options);
                    631: }
                    632: 
                    633: static void _string(Request& r, MethodParams& params) {
                    634:        Json_options json(&r);
                    635: 
                    636:        if(params.count() == 2)
                    637:                if(HashStringValue* options=params.as_hash(1)) {
1.47      moko      638:                        json.params=&params[1];
1.6       misha     639:                        HashStringValue* methods=new HashStringValue();
1.14      misha     640:                        HashStringValue* vvalue;
1.6       misha     641:                        for(HashStringValue::Iterator i(*options); i; i.next() ){
                    642:                                String::Body key=i.key();
                    643:                                Value* value=i.value();
                    644:                                if(key == "skip-unknown"){
1.46      moko      645:                                        json.skip_unknown=r.process(*value).as_bool();
1.50      moko      646:                                } else if(key == "one-line"){
                    647:                                        json.one_line=r.process(*value).as_bool();
1.6       misha     648:                                } else if(key == "date" && value->is_string()){
                    649:                                        const String& svalue=value->as_string();
                    650:                                        if(!json.set_date_format(svalue))
1.38      moko      651:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'sql-string', 'gmt-string', 'iso-string' or 'unix-timestamp'");
1.8       moko      652:                                } else if(key == "indent"){
1.26      moko      653:                                        if(value->is_string()){
                    654:                                                json.indent=value->as_string().cstr();
1.63      moko      655:                                                json.json_string_recursion=strlen(json.indent);
1.46      moko      656:                                        } else json.indent=r.process(*value).as_bool() ? "" : NULL;
1.6       misha     657:                                } else if(key == "table" && value->is_string()){
                    658:                                        const String& svalue=value->as_string();
                    659:                                        if(!json.set_table_format(svalue))
1.13      moko      660:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.59      moko      661:                                } else if(key == "array" && value->is_string()){
                    662:                                        const String& svalue=value->as_string();
                    663:                                        if(!json.set_array_format(svalue))
                    664:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'array', 'object' or 'compact'");
1.6       misha     665:                                } else if(key == "file" && value->is_string()){
                    666:                                        const String& svalue=value->as_string();
                    667:                                        if(!json.set_file_format(svalue))
1.19      misha     668:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'base64', 'text' or 'stat'");
1.32      misha     669:                                } else if(key == "void" && value->is_string()){
                    670:                                        const String& svalue=value->as_string();
                    671:                                        if(!json.set_void_format(svalue))
                    672:                                                throw Exception(PARSER_RUNTIME, &svalue, "must be 'string' or 'null'");
1.14      misha     673: #ifdef XML
                    674:                                } else if(key == "xdoc" && (vvalue = value->get_hash())){
1.24      moko      675:                                        json.xdoc_options=new XDocOutputOptions();
                    676:                                        json.xdoc_options->append(r, vvalue);
1.14      misha     677: #endif
1.6       misha     678:                                } else if(Junction* junction=value->get_junction()){
1.49      moko      679:                                        if(!junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.13      moko      680:                                                throw Exception(PARSER_RUNTIME, 0, "$.%s must be parser method with 3 parameters", key.cstr());
1.6       misha     681:                                        methods->put(key, value);
1.68      moko      682:                                } else
                    683:                                        throw Exception(PARSER_RUNTIME, 0, CALLED_WITH_INVALID_OPTION);
1.6       misha     684:                        }
1.22      moko      685: 
                    686:                        // special handling for $._default 
1.61      moko      687:                        if(VHashBase* vhash=dynamic_cast<VHashBase*>(&params[1]))
1.22      moko      688:                                if(Value* value=vhash->get_default()) {
1.34      misha     689:                                        if(!value->is_string()){
1.43      moko      690:                                                Junction* junction=value->get_junction();
1.49      moko      691:                                                if(!junction || !junction->method || !junction->method->params_names || junction->method->params_count != 3)
1.42      moko      692:                                                        throw Exception(PARSER_RUNTIME, 0, "$._default must be string or parser method with 3 parameters");
1.34      misha     693:                                        }
1.22      moko      694:                                        json.default_method=value;
                    695:                                }
                    696: 
1.6       misha     697:                        if(methods->count())
                    698:                                json.methods=methods;
                    699:                }
1.14      misha     700: 
1.46      moko      701:        const String& result_string=value_json_string(String::Body(), r.process(params[0]), json);
1.28      moko      702:        String::Body result_body=result_string.cstr_to_string_body_untaint(String::L_JSON, r.connection(false), &r.charsets);
1.50      moko      703:        if(json.one_line){
                    704:                char *result=result_body.cstrm();
                    705:                for(char *c=result;*c;c++)
                    706:                        if(*c=='\n')
                    707:                                *c=' ';
1.67      moko      708:                result_body=String::Body(result);
1.50      moko      709:        }
1.48      moko      710:        r.write(*new String(result_body, String::L_AS_IS));
1.50      moko      711: }
1.6       misha     712: 
1.1       misha     713: // constructor
                    714: 
                    715: MJson::MJson(): Methoded("json") {
                    716:        add_native_method("parse", Method::CT_STATIC, _parse, 1, 2);
1.6       misha     717: 
1.58      moko      718:        add_native_method("string", Method::CT_STATIC, _string, 1, 2);
1.1       misha     719: }

E-mail: