Advertisement
yuvarajupadhyaya

ParseJSON Function

Apr 25th, 2022
3,078
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
SQL 10.96 KB | None | 0 0
  1. CREATE FUNCTION [dbo].[parseJSON]( @JSON NVARCHAR(MAX))
  2. /*
  3. -- Create by Phil Factor    15 November 2010
  4. -- Update by Kaden Mai      21 August 2014
  5. -- How to Use   http://mtkcode.blogspot.com/2014/08/parse-json-string-by-sql-script.html
  6. */
  7. RETURNS @hierarchy TABLE
  8.   (
  9.    element_id INT IDENTITY(1, 1) NOT NULL, /* internal surrogate primary key gives the order of parsing and the list order */
  10.    sequenceNo [INT] NULL, /* the place in the sequence for the element */
  11.    parent_ID INT,/* if the element has a parent then it is in this column. The document is the ultimate parent, so you can get the structure from recursing from the document */
  12.    Object_ID INT,/* each list or object has an object id. This ties all elements to a parent. Lists are treated as objects here */
  13.    NAME NVARCHAR(2000),/* the name of the object */
  14.    StringValue NVARCHAR(MAX) NOT NULL,/*the string representation of the value of the element. */
  15.    ValueType VARCHAR(10) NOT NULL /* the declared type of the value represented as a string in StringValue*/
  16.   )
  17. AS
  18. BEGIN
  19.   DECLARE
  20.     @FirstObject INT, --the index of the first open bracket found in the JSON string
  21.     @OpenDelimiter INT,--the index of the next open bracket found in the JSON string
  22.     @NextOpenDelimiter INT,--the index of subsequent open bracket found in the JSON string
  23.     @NextCloseDelimiter INT,--the index of subsequent close bracket found in the JSON string
  24.     @TYPE NVARCHAR(10),--whether it denotes an object or an array
  25.     @NextCloseDelimiterChar CHAR(1),--either a '}' or a ']'
  26.     @Contents NVARCHAR(MAX), --the unparsed contents of the bracketed expression
  27.     @START INT, --index of the start of the token that you are parsing
  28.     @END INT,--index of the end of the token that you are parsing
  29.     @param INT,--the parameter at the end of the next Object/Array token
  30.     @EndOfName INT,--the index of the start of the parameter at end of Object/Array token
  31.     @token NVARCHAR(200),--either a string or object
  32.     @VALUE NVARCHAR(MAX), -- the value as a string
  33.     @SequenceNo INT, -- the sequence number within a list
  34.     @name NVARCHAR(200), --the name as a string
  35.     @parent_ID INT,--the next parent ID to allocate
  36.     @lenJSON INT,--the current length of the JSON String
  37.     @characters NCHAR(36),--used to convert hex to decimal
  38.     @RESULT BIGINT,--the value of the hex symbol being parsed
  39.     @INDEX SMALLINT,--used for parsing the hex value
  40.     @Escape INT --the index of the next escape character
  41.    
  42.  
  43.   DECLARE @Strings TABLE /* in this temporary table we keep all strings, even the names of the elements, since they are 'escaped' in a different way, and may contain, unescaped, brackets denoting objects or lists. These are replaced in the JSON string by tokens representing the string */
  44.     (
  45.      String_ID INT IDENTITY(1, 1),
  46.      StringValue NVARCHAR(MAX)
  47.     )
  48.   SELECT--initialise the characters to convert hex to ascii
  49.     @characters='0123456789abcdefghijklmnopqrstuvwxyz',
  50.     @SequenceNo=0, --set the sequence no. to something sensible.
  51.   /* firstly we process all strings. This is done because [{} and ] aren't escaped in strings, which complicates an iterative parse. */
  52.     @parent_ID=0;
  53.   WHILE 1=1 --forever until there is nothing more to do
  54.     BEGIN
  55.       SELECT
  56.         @START=PATINDEX('%[^a-zA-Z]["]%', @json COLLATE SQL_Latin1_General_CP850_Bin);--next delimited string
  57.       IF @START=0 BREAK --no more so drop through the WHILE loop
  58.       IF SUBSTRING(@json, @START+1, 1)='"'
  59.         BEGIN --Delimited Name
  60.           SET @START=@START+1;
  61.           SET @END=PATINDEX('%[^\]["]%', RIGHT(@json, LEN(@json+'|')-@START) COLLATE SQL_Latin1_General_CP850_Bin);
  62.         END
  63.       IF @END=0 --no end delimiter to last string
  64.         BREAK --no more
  65.       SELECT @token=SUBSTRING(@json, @START+1, @end-1)
  66.       --now put in the escaped control characters
  67.       SELECT @token=REPLACE(@token, FROMString, TOString)
  68.       FROM
  69.         (SELECT
  70.           '\"' AS FromString, '"' AS ToString
  71.          UNION ALL SELECT '\\', '\'
  72.         UNION ALL SELECT '\/', '/'
  73.         UNION ALL SELECT '\b', CHAR(08)
  74.         UNION ALL SELECT '\f', CHAR(12)
  75.         UNION ALL SELECT '\n', CHAR(10)
  76.         UNION ALL SELECT '\r', CHAR(13)
  77.         UNION ALL SELECT '\t', CHAR(09)
  78.        ) substitutions
  79.      SELECT @result=0, @escape=1
  80.  --Begin to take out any hex escape codes
  81.      WHILE @escape>0
  82.        BEGIN
  83.          SELECT @index=0,
  84.          --find the next hex escape sequence
  85.          @escape=PATINDEX('%\x[0-9a-f][0-9a-f][0-9a-f][0-9a-f]%', @token collate SQL_Latin1_General_CP850_Bin)
  86.          IF @escape>0 --if there is one
  87.            BEGIN
  88.              WHILE @index<4 --there are always four digits to a \x sequence  
  89.                BEGIN
  90.                  SELECT --determine its value
  91.                    @result=@result+POWER(16, @index)
  92.                    *(CHARINDEX(SUBSTRING(@token, @escape+2+3-@index, 1),
  93.                                @characters)-1), @index=@index+1 ;
  94.        
  95.                END
  96.                -- and replace the hex sequence by its unicode value
  97.              SELECT @token=STUFF(@token, @escape, 6, NCHAR(@result))
  98.            END
  99.        END
  100.      --now store the string away
  101.      INSERT INTO @Strings (StringValue) SELECT @token
  102.      -- and replace the string with a token
  103.      SELECT @JSON=STUFF(@json, @start, @end+1,
  104.                    '@string'+CONVERT(NVARCHAR(5), @@identity))
  105.    END
  106.  -- all strings are now removed. Now we find the first leaf.
  107.  WHILE 1=1  --forever until there is nothing more to do
  108.  BEGIN
  109.  
  110.  SELECT @parent_ID=@parent_ID+1
  111.  --find the first object or list by looking for the open bracket
  112.  SELECT @FirstObject=PATINDEX('%[{[[]%', @json collate SQL_Latin1_General_CP850_Bin)--object or array
  113.  IF @FirstObject = 0 BREAK
  114.  IF (SUBSTRING(@json, @FirstObject, 1)='{')
  115.    SELECT @NextCloseDelimiterChar='}', @type='object'
  116.  ELSE
  117.    SELECT @NextCloseDelimiterChar=']', @type='array'
  118.  SELECT @OpenDelimiter=@firstObject
  119.  
  120.  WHILE 1=1 --find the innermost object or list...
  121.    BEGIN
  122.      SELECT
  123.        @lenJSON=LEN(@JSON+'|')-1
  124.  --find the matching close-delimiter proceeding after the open-delimiter
  125.      SELECT
  126.        @NextCloseDelimiter=CHARINDEX(@NextCloseDelimiterChar, @json,
  127.                                      @OpenDelimiter+1)
  128.  --is there an intervening open-delimiter of either type
  129.      SELECT @NextOpenDelimiter=PATINDEX('%[{[[]%',
  130.             RIGHT(@json, @lenJSON-@OpenDelimiter)collate SQL_Latin1_General_CP850_Bin)--object
  131.      IF @NextOpenDelimiter=0
  132.        BREAK
  133.      SELECT @NextOpenDelimiter=@NextOpenDelimiter+@OpenDelimiter
  134.      IF @NextCloseDelimiter<@NextOpenDelimiter
  135.        BREAK
  136.      IF SUBSTRING(@json, @NextOpenDelimiter, 1)='{'
  137.        SELECT @NextCloseDelimiterChar='}', @type='object'
  138.      ELSE
  139.        SELECT @NextCloseDelimiterChar=']', @type='array'
  140.      SELECT @OpenDelimiter=@NextOpenDelimiter
  141.    END
  142.  ---and parse out the list or name/value pairs
  143.  SELECT
  144.    @contents=SUBSTRING(@json, @OpenDelimiter+1,
  145.                        @NextCloseDelimiter-@OpenDelimiter-1)
  146.  SELECT
  147.    @JSON=STUFF(@json, @OpenDelimiter,
  148.                @NextCloseDelimiter-@OpenDelimiter+1,
  149.                '@'+@type+CONVERT(NVARCHAR(5), @parent_ID))
  150.  WHILE (PATINDEX('%[A-Za-z0-9@+.e]%', @contents collate SQL_Latin1_General_CP850_Bin))<>0
  151.    BEGIN
  152.      IF @Type='Object' --it will be a 0-n list containing a string followed by a string, number,boolean, or null
  153.        BEGIN
  154.          SELECT
  155.            @SequenceNo=0,@end=CHARINDEX(':', ' '+@contents)--if there is anything, it will be a string-based name.
  156.          SELECT  @start=PATINDEX('%[^A-Za-z@][@]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)--AAAAAAAA
  157.          SELECT @token=SUBSTRING(' '+@contents, @start+1, @End-@Start-1),
  158.            @endofname=PATINDEX('%[0-9]%', @token collate SQL_Latin1_General_CP850_Bin),
  159.            @param=RIGHT(@token, LEN(@token)-@endofname+1)
  160.          SELECT
  161.            @token=LEFT(@token, @endofname-1),
  162.            @Contents=RIGHT(' '+@contents, LEN(' '+@contents+'|')-@end-1)
  163.          SELECT  @name=stringvalue FROM @strings
  164.            WHERE string_id=@param --fetch the name
  165.        END
  166.      ELSE
  167.        SELECT @Name=null,@SequenceNo=@SequenceNo+1
  168.      SELECT
  169.        @end=CHARINDEX(',', @contents)-- a string-token, object-token, list-token, number,boolean, or null
  170.      IF @end=0
  171.        SELECT  @end=PATINDEX('%[A-Za-z0-9@+.e][^A-Za-z0-9@+.e]%', @Contents+' ' collate SQL_Latin1_General_CP850_Bin)
  172.          +1
  173.       SELECT
  174.         @start=PATINDEX('%[^A-Za-z0-9@+.e][A-Za-z0-9@+.e][\-]%', ' '+@contents collate SQL_Latin1_General_CP850_Bin)
  175.         -- Edited: add more condition [\-] in order to detect negative number 08-20-2014
  176.      --select @start,@end, LEN(@contents+'|'), @contents
  177.      SELECT
  178.        @Value=RTRIM(SUBSTRING(@contents, @start, @End-@Start)),
  179.        @Contents=RIGHT(@contents+' ', LEN(@contents+'|')-@end)
  180.      IF SUBSTRING(@value, 1, 7)='@object'
  181.        INSERT INTO @hierarchy
  182.          (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
  183.          SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 8, 5),
  184.            SUBSTRING(@value, 8, 5), 'object'
  185.      ELSE
  186.        IF SUBSTRING(@value, 1, 6)='@array'
  187.          INSERT INTO @hierarchy
  188.            (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
  189.            SELECT @name, @SequenceNo, @parent_ID, SUBSTRING(@value, 7, 5),
  190.              SUBSTRING(@value, 7, 5), 'array'
  191.        ELSE
  192.          IF SUBSTRING(@value, 1, 7)='@string'
  193.            INSERT INTO @hierarchy
  194.              (NAME, SequenceNo, parent_ID, StringValue, ValueType)
  195.              SELECT @name, @SequenceNo, @parent_ID, stringvalue, 'string'
  196.              FROM @strings
  197.              WHERE string_id=SUBSTRING(@value, 8, 5)
  198.          ELSE
  199.            IF @value IN ('TRUE', 'FALSE')
  200.              INSERT INTO @hierarchy
  201.                (NAME, SequenceNo, parent_ID, StringValue, ValueType)
  202.                SELECT @name, @SequenceNo, @parent_ID, @value, 'BOOLEAN'
  203.            ELSE
  204.              IF @value='NULL'
  205.                INSERT INTO @hierarchy
  206.                  (NAME, SequenceNo, parent_ID, StringValue, ValueType)
  207.                  SELECT @name, @SequenceNo, @parent_ID, @value, 'NULL'
  208.              ELSE
  209.                IF PATINDEX('%[^0-9]%', @value collate SQL_Latin1_General_CP850_Bin)>0
  210.                  INSERT INTO @hierarchy
  211.                    (NAME, SequenceNo, parent_ID, StringValue, ValueType)
  212.                    SELECT @name, @SequenceNo, @parent_ID, @value, 'REAL'
  213.                ELSE
  214.                  INSERT INTO @hierarchy
  215.                    (NAME, SequenceNo, parent_ID, StringValue, ValueType)
  216.                    SELECT @name, @SequenceNo, @parent_ID, @value, 'INT'
  217.      if @Contents=' ' Select @SequenceNo=0
  218.    END
  219.  END
  220. INSERT INTO @hierarchy (NAME, SequenceNo, parent_ID, StringValue, Object_ID, ValueType)
  221.  SELECT '-',1, NULL, '', @parent_id-1, @type
  222. --
  223.   RETURN
  224. END
  225.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement