文字列遍歴実装のJson元の解析(Objective-C)

24301 ワード

文字列遍歴実装のJson元の解析(Objective-C)
1.はじめに
最近Objective-C(以下objcと略称する)を学んで、Json解析器を作って知識を固めて、ついでに手触りを練習します.OBjcのCocoaライブラリにNSJSONSSerializationツールクラスがあり、このクラスのapiで簡単にjson解析ができ、まだ効率が高いそうです.しかし,ここではこのクラスで実現するのではなく,最も原始的な文字列遍歴を用いて自分で解析を行った.厳密なテストがないので、崩れやすいとか、判断が間違っているとか、お許しください.
2.着手前の注意事項
今回の項目の入力はJson文字列(NSString)、出力は解析結果(NSDictionary、NSArrayまたはNSString)です.jsonフォーマットが正しくない場合はJson , 。が出力されます
objcでは、文字列の読み取り文字はどのように読みますか?単純にcharAtIndexを使うだけでは、異なる符号化形式で中国語を読むときにエラーが発生する可能性があり、unicharを返してNSStringと比較することはできません.したがって、私は長さ1のサブストリングを取って文字列を遍歴します.すなわち、[inputString substringWithRange: NSMakeRange(index, 1)]のように、このようにして1文字を正確に取ることができます.
このインプリメンテーションでは,再帰的に解析オブジェクトを生成する,すなわち,解析メソッドのインプリメンテーションで自分を呼び出す.主な原因は、json内のオブジェクトのvalueがオブジェクトである可能性があり、この形式は再帰的に実現するのに最も適しているからである.
3.フローチャート
4.実現
  • 特殊符号
  • を除去する.
    入力されたjson文字列には、リターン
    、改行\r、タブ\tなどが存在する可能性がありますので、これらの特殊文字を優先的に除去します.
    inputJson = [inputJson stringByReplacingOccurrencesOfString: @"
    "
    withString: @""]; // inputJson = [inputJson stringByReplacingOccurrencesOfString: @"\r" withString: @""]; // inputJson = [inputJson stringByReplacingOccurrencesOfString: @"\t" withString: @""]; // tab
  • 遍歴文字列
  • ここから正式に解析を開始しても、本人はどのようにフォーマット検証を行ってから解析を行うか考えていないので、解析しながら検証し、jsonのフォーマットに合わないところまで解析して解析を終了します.
    まず最初の非スペース文字から判断し、いくつかの可能性に分けて、{ -> 、[ -> 、false 、true、null " -> .いちいち判断が必要だ.
    {
    ここではまずトップの{}を外してから次の解析を行います.
    //      {},     ,     }      
    for (j = length-1; j > 0; j--) {
        rightChar = [inputJson substringWithRange: NSMakeRange(j, 1)];
        if ([rightChar isEqualToString: @" "]) {
            continue;
        }
        if (![rightChar isEqualToString: @"}"]) {
            return nil;
        } else {
            break;
        }
    }
    inputJson = [inputJson substringWithRange: NSMakeRange( i+1, j-i-1)];
    length = [inputJson length];
    i = 0;

    次に、1つのオブジェクトが空であってもよいし、1つ以上の"key":value(keyは任意の文字列であり、valueはオブジェクト、配列、数字、文字列などであってもよい)というフォーマットの文字列であってもよい.そうしないと、フォーマットは不正である.深く遍歴する前に、いくつかのflagを定義します.
    // rightQuote  key    ,leftQuote  key    ,hasColon  key-value     
    // firstKeyValue         key-value,metComma          ,foundValue    key         value
    BOOL rightQuote = NO, leftQuote = NO, hasColon = NO, firstKeyValue = YES, metComma = YES, foundValue = NO;

    そしてkeyを探して
    //    
    if ([leftChar isEqualToString: @" "]) {
        continue;
    }
    //                 
    if ( (![leftChar isEqualToString: @"\""]) && leftQuote == NO ) {
        //            key,         
        if (!firstKeyValue && [leftChar isEqualToString: @","] && metComma == NO) {
            metComma = YES;
            continue;
        }
        else {
            return nil;
        }
    }
    //  key,                  ,    key         if      
    if ([leftChar isEqualToString: @"\""] && metComma && leftQuote == NO) {
    
        leftQuote = YES;
    
        key = findString(inputJson, i, end);
        if (key == nil) {
            return nil;
        }
        rightQuote = YES;
        i = j;
        continue;
    }
    //             ,    
    else if ([leftChar isEqualToString: @"\""] && !metComma){
        return nil;
    }

    この中にfindString関数が現れて、この関数はカスタマイズして、コードは以下の通りです
    //          
    NSString * findString (NSString * input, NSUInteger star, NSUInteger *end) // end               
    {
        //     result    nil,            nil
        NSString * rightChar, * result = nil;
    
        NSUInteger length = [input length];
    
        //       
        for (*end = star+1; *end < length; *end+=1) {
            rightChar = [input substringWithRange: NSMakeRange(*end, 1)];
            if ([rightChar isEqualToString: @"\""]) {
                //                 
                if ([[input substringWithRange: NSMakeRange(*end-1, 1)] isEqualToString:@"\\"]) {
                    continue;
                }
    
                result = [input substringWithRange: NSMakeRange(star +1, *end- star -1)];
                break;
            }
        }
        return result;
    } // end findString

    keyを見つけたら:を探し始めます.keyの後はコロン、コロンの後はvalueと
    //          ,   key              json
    if ( ![leftChar isEqualToString: @":"] && !hasColon) {
        return nil;
    }
    //   
    if ([leftChar isEqualToString: @":"]) {
    
        hasColon = YES;
        foundValue = NO;
    
        //   value
        for (i++ ; i < length; i++) {
            leftChar = [inputJson substringWithRange: NSMakeRange(i, 1)];
            //    
            if ([leftChar isEqualToString: @" "]) {
                continue;
            }
    
            //            ,     value(      
            foundValue = YES;
    
            //    value       
            ...
    
        }
        //     value,    
        if (foundValue == NO) {
            return nil;
        }
        if (key == nil || value == nil) {
            return nil;
        }
        //    value,      
        [result setObject: value forKey: key];
        //  flag,      key-value   
        firstKeyValue = NO; metComma = NO;
        leftQuote = NO; rightQuote = NO; hasColon = NO;
        //i value       ,  for    i++
        i = j;
    }

    valueを探すにも状況を分けて議論する必要がある
    //value      
    if ([leftChar isEqualToString: @"\""]) {
        value = findString(inputJson, i, end);
        if (value == nil) {
            return nil;
        }
        break;
    }
    
    //value     
    if ([leftChar isEqualToString: @"["]) {
        //     "]"        ,findCorrespondBracket       
        int bracketCount = findCorrespondBracket(inputJson, @"[", @"]", i, end);
        if (bracketCount != 0) { //           
            return nil;
        }
        //                   
        value = [VJsonPaser parseToArray: [inputJson substringWithRange: NSMakeRange(i, j-i+1)]];
        break;
    }
    
    //value     
    if ([leftChar isEqualToString: @"{"]) {
        int bracketCount = findCorrespondBracket(inputJson, @"{", @"}", i, end);
        if (bracketCount != 0) { //           
            return nil;
        }
        value = [VJsonPaser parseToDictionary: [inputJson substringWithRange:
                                                NSMakeRange(i, j-i+1)]];
        break;
    }
    
    //        ,checkOtherLegal       ,     
    value = checkOtherLegal(inputJson, i, end, leftChar);
    if (value != nil) {
        break;
    }
    
    //           value
    return nil;
    findCorrespondBracketはカスタムメソッドであり、以下のように実現される.
    //             
    int findCorrespondBracket(NSString * input, NSString* leftBracket, NSString* rightBracket, NSUInteger star, NSUInteger* end)
    {
        int bracketCount = 1;
        NSString * rightChar;
        NSUInteger length = [input length];
    
        for( *end = star+1; *end < length; *end+=1) {
    
            rightChar = [input substringWithRange: NSMakeRange(*end, 1)];
    
            //      ,              ,    
            if ([rightChar isEqualToString: @"\""]) {
                star = *end;
                NSString* str = findString(input, star, end);
                if (str == nil) {
                    return -1;
                }
                continue;
            }
    
            if ([rightChar isEqualToString: leftBracket]) {
                bracketCount ++;
            }
            else if ([rightChar isEqualToString: rightBracket]) {
                bracketCount --;
                if (bracketCount == 0) {
                    //  j   value     
                    break;
                }
            }
    
        }
        return bracketCount;
    } // end findCorrespondBracket
    checkOtherLegal関数には、nulltrue、または666.66のような他の雑砕状況の検証が含まれており、スコア状況の議論も含まれている.
    //        
    NSString* checkOtherLegal(NSString* input, NSUInteger star, NSUInteger* end, NSString* leftChar)
    {
        NSString *value = nil, *rightChar;
        NSUInteger length = [input length];
    
        //null
        if ([leftChar isEqualToString: @"n"]) {
            if (length < star+4) {
                return nil;
            }
            leftChar = [input substringWithRange: NSMakeRange(star, 4)];
            if (![leftChar isEqualToString: @"null"]) {
                return nil;
            }
            value = @"null";
            *end = star+3;
        }
    
        //true
        else if ([leftChar isEqualToString: @"t"]) {
            if (length < star+4) {
                return nil;
            }
            leftChar = [input substringWithRange: NSMakeRange(star, 4)];
            if (![leftChar isEqualToString: @"true"]) {
                return nil;
            }
            value = @"true";
            *end = star+3;
        }
    
        //false
        else if ([leftChar isEqualToString: @"f"]) {
            if (length < star+5) {
                return nil;
            }
            leftChar = [input substringWithRange: NSMakeRange(star, 5)];
            if (![leftChar isEqualToString: @"false"]) {
                return nil;
            }
            value = @"false";
            *end = star+4;
        }
    
        //  
        NSPredicate* numberPredicate = [NSPredicate predicateWithFormat: @"SELF IN {'0','1','2','3','4','5','6','7','8','9'}"];
    
        if ([numberPredicate evaluateWithObject: leftChar]) {
    
            *end = star+1;
    
            //   0      ,  ,  ,      
            if ([leftChar isEqualToString: @"0"]) {
                rightChar = [input substringWithRange: NSMakeRange(*end, 1)];
                if (![rightChar isEqualToString: @"."] && ![rightChar isEqualToString:@","] && ![rightChar isEqualToString:@" "]) {
                    return nil;
                }
            }
    
            BOOL alreadyDot = NO; //          
    
            for( ; *end < length; *end += 1) {
    
                rightChar = [input substringWithRange: NSMakeRange(*end, 1)];
    
                if ([rightChar isEqualToString: @"."]) {
                    //        
                    if (alreadyDot) {
                        return nil;
                    }
                    alreadyDot = YES;
    
                    //             
                    if (length <= *end+1) {
                        return nil;
                    }
                    rightChar = [input substringWithRange: NSMakeRange(*end+1, 1)];
                    if (![numberPredicate evaluateWithObject: rightChar]) {
                        return nil;
                    }
    
                    continue;
                }
    
                //     
                else if ([numberPredicate evaluateWithObject:rightChar]) {
                    continue;
                }
    
                //        value  
                else if ([rightChar isEqualToString:@","] || [rightChar isEqualToString:@" "]) {
                    break;
                }
    
                //          
                else {
                    return nil;
                }
    
            }
            value = [input substringWithRange: NSMakeRange(star, *end-star)];
            *end -= 1; //           
        }
        return value;
    } // end checkOtherLegal

    key-valueを見つけたらforループを続け、次のkey-valueを探します.
    [
    最初に出会ったのが配列で、オブジェクトとは違う場合は、解析配列の方法をもう一つ書くしかありません.
    同様に[]を先に取り除く
    //      [],     ,     ]      
    for (; j > 0; j--) {
        rightChar = [inputString substringWithRange: NSMakeRange(j, 1)];
        if ([rightChar isEqualToString: @" "]) {
            continue;
        }
        if (![rightChar isEqualToString: @"]"]) {
            return nil;
        } else {
            break;
        }
    }
    inputString = [inputString substringWithRange: NSMakeRange( i+1, j-i-1)];
    length = [inputString length];

    次に文字を深く遍歴し、配列の各サブアイテムの分割を開始します.複数のサブアイテムは,によって分割されるため、,に対していくつかの特判を先に行う必要があります.
    //         
    if ([leftChar isEqualToString: @" "]) {
        continue;
    }
    //                  
    if (!firstObject && [leftChar isEqualToString: @","]) {
        if (metComma == YES) {
            return nil;
        }
        metComma = YES;
        //            
        foundObject = NO;
        continue;
    }
    else if (firstObject && [leftChar isEqualToString: @","]) {
        return nil;
    }

    次に、サブアイテムを解析します.サブアイテムには状況が必要です.オブジェクト、配列、文字列などかもしれません(またあなたたちです!)
    NSObject *obj = nil;
    
    if ([leftChar isEqualToString: @"{"] && metComma) {
        //     
        int bracketCount = findCorrespondBracket(inputString, @"{", @"}", i, end);
        if (bracketCount != 0) {
            return nil;
        }
        obj = [VJsonPaser parseToDictionary:
                         [inputString substringWithRange: NSMakeRange(i, j-i+1)]];
    }
    else if ([leftChar isEqualToString: @"["] && metComma) {
        //     
        int bracketCount = findCorrespondBracket(inputString, @"[", @"]", i, end);
        if (bracketCount != 0) {
            return nil;
        }
        obj = [VJsonPaser parseToArray: [inputString substringWithRange: NSMakeRange(i, j-i+1)]];
    }
    //            ,                
    else if ([leftChar isEqualToString: @"\""] && metComma) {
        obj = findString(inputString, i, end);
    }
    //  ,     
    else {
        obj = checkOtherLegal(inputString, i, end, leftChar);
    }
    
    if (obj == nil) {
        return nil;
    }
    [result addObject:obj];
    //  flag,        
    foundObject = YES;
    firstObject = NO;
    metComma = NO;
    i = j;

    ここまでで配列を解析するとか、コード量はオブジェクトの解析に対して少し少ないですが、やはり頭が痛いです.

    「その他」の2つの字を見るとcheckOtherLegalを使うことがわかります.幸いにも1つの関数にカプセル化されていて、そんなに長いコードをコピーする必要はありません.
    else if ([leftChar isEqualToString: @"\""]) {
        return findString(inputJson, i, end);
    }
    else {
        NSString* str = checkOtherLegal(inputJson, i, end, leftChar);
        if (str == nil) {
            return nil;
        }
        return str;
    }

    5.まとめ
    ここまで来れば、コードがそんなに長くても誰も見ていないので、ノートを書くつもりです.
    文末にgithubを添付します