In the new framework there is function that will convert a Dictionary or Auto() to JSON, and it’s preferable to the old JSONItem because 1) it’s easier to use and 2) it generates better JSON. The only deficiency is that you cannot generate “pretty” JSON.
One solution is to covert the JSON text to a JSONItem, set Compact to False and generate JSON again but I don’t particularly care for that since the old JSONItem suffers from some bugs that will never be fixed because of the replacement. Further, once we have the text we need, it only needs to be formatted, not generated again.
With that goal, I came up with this function. It uses a RegEx (not available in iOS yet) to insert EOL’s at the appropriate places, then pads the lines appropriately.
I’ve only done some basic testing so this is subject to change.
First the pattern, stored in a constant:
(?x)
(?(DEFINE)
(?<ob>[[{])
(?<cb>[\\]}])
(?<string>"(?:\\\"|[^"])*")
(?<key>(?&string))
(?<val>(?&string)|[^,}\\]]+)
)
\\[\\],? | \\{\\},? |
(?&ob) |
(?&key) : (?&ob) |
(?&key) : (?&val) ,? |
(?&val) ,? |
(?&cb) ,?
Now the function:
Function ToJSON_MTC(Extends dict As Xojo.Core.Dictionary, pretty As Boolean = False) As Text
const kEOL = &u0A
dim json as Text = Xojo.Data.GenerateJSON( dict )
#if not TargetiOS
if pretty then
static rx as RegEx
if rx is nil then
rx = new RegEx
rx.SearchPattern = kPrettyJSONRegEx
rx.ReplacementPattern = "$&" + kEOL
rx.Options.ReplaceAllMatches = true
end if
dim jString as string = rx.Replace( json )
dim lines() as string = jString.Split( kEOL )
dim indent as string = ""
dim pad as string = " "
for i as integer = 0 to lines.Ubound
dim thisLine as string = lines( i )
dim nextIndent as string = indent
dim firstChar as string = thisLine.Left( 1 )
dim lastChar as string = thisLine.Right( 1 )
dim firstTwo as string = thisLine.Left( 2 )
if firstTwo = "[]" or firstTwo = "{}" then
//
// Do nothing
//
elseif firstChar = "{" or firstChar = "[" or lastChar = "{" or lastChar = "[" then
nextIndent = indent + pad
elseif firstChar = "}" or firstChar = "]" then
indent = indent.LeftB( indent.LenB - pad.LenB )
nextIndent = indent
end if
thisLine = indent + thisLine
lines( i ) = thisLine
indent = nextIndent
next
jString = join( lines, kEOL )
json = jString.ToText
end if
#endif
return json
End Function
After more testing, I’ve revised the RegEx and came up with code that works in iOS too. First the pattern:
(?x)
(?(DEFINE)
(?<ob>[[{])
(?<cb>[\\]}])
(?<string>"(?:\\\"|[^"])*")
(?<key>(?&string))
(?<val>(?&string)|[^,}\\]]+)
)
\\[\\],? | \\{\\},? |
(?&ob) |
(?&key) : (?:\\{\\} | \\[\\]) ,? |
(?&key) : (?&ob) |
(?&key) : (?&val) ,? |
(?&val) ,? |
(?&cb) ,?
Here is the main code, called from Extends on both an Auto() and Xojo.Core.Dictionary:
Private Function ToJSON(o As Auto, pretty As Boolean = False) As Text
dim json as text = Xojo.Data.GenerateJSON( o )
if pretty then
#if TargetiOS
json = PrettyJSONiOS( json )
#else
json = PrettyJSON( json )
#endif
end if
return json
End Function
And the pretty-making code:
Private Function PrettyJSON(json As Text) As Text
#if TargetWin32
const kEOL as string = &u0D + &u0A
#else
const kEOL as string = &u0A
#endif
static rx as RegEx
if rx is nil then
rx = new RegEx
rx.SearchPattern = kPrettyJSONRegEx
rx.ReplacementPattern = "$&" + kEOL
rx.Options.ReplaceAllMatches = true
end if
dim jString as string = rx.Replace( json )
dim lines() as string = jString.Split( kEOL )
dim newLines() as string
const kPad as string = " "
dim padLenB as integer = kPad.LenB
dim indent as string = ""
for i as integer = 0 to lines.Ubound
dim thisLine as string = lines( i )
dim nextIndent as string = indent
dim firstChar as string = thisLine.Left( 1 )
dim lastChar as string = thisLine.Right( 1 )
dim firstTwo as string = thisLine.Left( 2 )
if firstTwo = "[]" or firstTwo = "{}" then
//
// Do nothing
//
elseif firstChar = "{" or firstChar = "[" or lastChar = "{" or lastChar = "[" then
nextIndent = indent + kPad
elseif firstChar = "}" or firstChar = "]" then
indent = indent.LeftB( indent.LenB - padLenB )
nextIndent = indent
end if
newLines.Append indent
newLines.Append thisLine
newLines.Append kEOL
indent = nextIndent
next
jString = join( newLines, "" )
json = jString.ToText
return json.TrimRight
End Function
Private Function PrettyJSONiOS(json As Text) As Text
const kEOL as text = &u0A
dim chars() as text
for each char as text in json.Characters
chars.Append char
next
dim newChars() as text
dim indent as text
dim kPad as text = " "
dim padLen as integer = kPad.Length
dim inString as boolean
dim charIndex as integer = 0
while charIndex <= chars.Ubound
dim char as text = chars( charIndex )
dim nextChar as text = if( charIndex = chars.Ubound, "", chars( charIndex + 1 ) )
if char = """" then
newChars.Append char
inString = not inString
elseif inString and char = "\" then
newChars.Append char
newChars.Append nextChar
charIndex = charIndex + 1
elseif inString then
newChars.Append char
elseif char = "{" and nextChar = "}" then
newChars.Append "{}"
charIndex = charIndex + 1
elseif char = "[" and nextChar = "]" then
newChars.Append "[]"
charIndex = charIndex + 1
elseif char = "," then
newChars.Append char
newChars.Append kEOL
newChars.Append indent
elseif char = "[" or char = "{" then
newChars.Append char
newChars.Append kEOL
indent = indent + kPad
newChars.Append indent
elseif char = "]" or char = "}" then
newChars.Append kEOL
indent = indent.Left( indent.Length - padLen )
newChars.Append indent
newChars.Append char
else
newChars.Append char
end if
charIndex = charIndex + 1
wend
json = Text.Join( newChars, "" )
return json
End Function
I’ve compared the output from a fairly complex JSON using each version to JSONItem.ToString and they are identical.
I also ported some C# code for formatting JSON to Xojo a couple months ago:
Format JSON