LoadJSONC()

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 6866
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

LoadJSONC()

Post by infratec »

To make it possible to load JSON files with comments inside.
It is not an official JSON extension, but some companies does this.

To make it fast and without additional memory usage, I used the memory of the string and replace all comments with spaces.
Spaces are allowed inside of a JSON, so I can afterwards using ParseJSON() as usual.
I'm using a state machine to detect the different possibilities.

Code: Select all

;
; LoadJSONC()
;
; https://www.purebasic.fr/english/viewtopic.php?t=83647
;
; V1.0.1 2024.02.24 allow single /
; V1.0.0 2024.02.23 first version
;

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf


Enumeration LoadJSONC_States
  #LoadJSONC_State_Search
  #LoadJSONC_State_FirstSlashFound
  #LoadJSONC_State_SecondSlashFound
  #LoadJSONC_State_FirstAsteriskFound
  #LoadJSONC_State_SecondAsteriskFound
EndEnumeration


Procedure.i LoadJSONC(JSON.i, Filename$, Flags.i=0)
  
  Protected File.i, Format.i, JSON$, *CharPtr.Character, State.i, EscapeFlag.i
  
  
  File = ReadFile(#PB_Any, Filename$)
  If File
    Format = ReadStringFormat(File)
    JSON$ = ReadString(File, Format|#PB_File_IgnoreEOL)
    
    CloseFile(File)
    
    If JSON$ <> ""
      *CharPtr = @JSON$
      While *CharPtr\c <> #Null
        
        Select State
          Case #LoadJSONC_State_Search
            Select *CharPtr\c 
              Case '/'
                If Not EscapeFlag
                  ; maybe start o a comment
                  State = #LoadJSONC_State_FirstSlashFound
                EndIf
              Case '\'
                ; the next character is escaped -> ignore it
                EscapeFlag = #True
              Default
                EscapeFlag = #False
            EndSelect
            
          Case #LoadJSONC_State_FirstSlashFound
            Select *CharPtr\c
              Case '/'
                ; // detected -> replace them
                *CharPtr - SizeOf(Character)
                *CharPtr\c = ' '
                *CharPtr + SizeOf(Character)
                *CharPtr\c = ' '
                State = #LoadJSONC_State_SecondSlashFound
              Case '*'
                ; /* detected -> replace them
                *CharPtr - SizeOf(Character)
                *CharPtr\c = ' '
                *CharPtr + SizeOf(Character)
                *CharPtr\c = ' '
                State = #LoadJSONC_State_FirstAsteriskFound
              Default
                State = #LoadJSONC_State_Search
            EndSelect
            
          Case #LoadJSONC_State_SecondSlashFound
            ; wait until end of line is reached
            If *CharPtr\c = #LF Or *CharPtr\c = #CR
              State = #LoadJSONC_State_Search
            Else
              *CharPtr\c = ' '
            EndIf
            
          Case #LoadJSONC_State_FirstAsteriskFound
            ; wait for second * -> replace everything
            If *CharPtr\c = '*'
              State = #LoadJSONC_State_SecondAsteriskFound
            EndIf
            *CharPtr\c = ' '
            
          Case #LoadJSONC_State_SecondAsteriskFound
            If *CharPtr\c = '/'
              ; */ found -> replace it and start searching for comments
              *CharPtr\c = ' '
              State = #LoadJSONC_State_Search
            Else
              ; not a comment termination -> replace it ands wait for */
              *CharPtr\c = ' '
              State = #LoadJSONC_State_FirstAsteriskFound
            EndIf
            
        EndSelect
        
        *CharPtr + SizeOf(Character)
      Wend
      
      JSON = ParseJSON(JSON, JSON$, Flags)
    EndIf
    
  EndIf
  
  ProcedureReturn JSON
  
EndProcedure



CompilerIf #PB_Compiler_IsMainFile
  
  Define Filename$, JSON.i
  
  
  Filename$ = OpenFileRequester("Choose a JSONC file", "", "JSONC|*.json;*jsonc", 0)
  If Filename$
    JSON = LoadJSONC(#PB_Any, Filename$)
    If JSON
      Debug ComposeJSON(JSON, #PB_JSON_PrettyPrint)
      FreeJSON(JSON)
    EndIf
  EndIf
  
CompilerEndIf
An example JSON:

Code: Select all

/*
 * Description 
*/
{
    // rainbows
    "uni/corn": /* ? */ "c\/*/ake"
}