├── .gitignore ├── LICENSE ├── README.md ├── json.vb └── monero.png /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | # Uncomment if you have tasks that create the project's static files in wwwroot 27 | #wwwroot/ 28 | 29 | # MSTest test Results 30 | [Tt]est[Rr]esult*/ 31 | [Bb]uild[Ll]og.* 32 | 33 | # NUNIT 34 | *.VisualState.xml 35 | TestResult.xml 36 | 37 | # Build Results of an ATL Project 38 | [Dd]ebugPS/ 39 | [Rr]eleasePS/ 40 | dlldata.c 41 | 42 | # DNX 43 | project.lock.json 44 | artifacts/ 45 | 46 | *_i.c 47 | *_p.c 48 | *_i.h 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.tmp_proj 63 | *.log 64 | *.vspscc 65 | *.vssscc 66 | .builds 67 | *.pidb 68 | *.svclog 69 | *.scc 70 | 71 | # Chutzpah Test files 72 | _Chutzpah* 73 | 74 | # Visual C++ cache files 75 | ipch/ 76 | *.aps 77 | *.ncb 78 | *.opendb 79 | *.opensdf 80 | *.sdf 81 | *.cachefile 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | *.vspx 87 | *.sap 88 | 89 | # TFS 2012 Local Workspace 90 | $tf/ 91 | 92 | # Guidance Automation Toolkit 93 | *.gpState 94 | 95 | # ReSharper is a .NET coding add-in 96 | _ReSharper*/ 97 | *.[Rr]e[Ss]harper 98 | *.DotSettings.user 99 | 100 | # JustCode is a .NET coding add-in 101 | .JustCode 102 | 103 | # TeamCity is a build add-in 104 | _TeamCity* 105 | 106 | # DotCover is a Code Coverage Tool 107 | *.dotCover 108 | 109 | # NCrunch 110 | _NCrunch_* 111 | .*crunch*.local.xml 112 | nCrunchTemp_* 113 | 114 | # MightyMoose 115 | *.mm.* 116 | AutoTest.Net/ 117 | 118 | # Web workbench (sass) 119 | .sass-cache/ 120 | 121 | # Installshield output folder 122 | [Ee]xpress/ 123 | 124 | # DocProject is a documentation generator add-in 125 | DocProject/buildhelp/ 126 | DocProject/Help/*.HxT 127 | DocProject/Help/*.HxC 128 | DocProject/Help/*.hhc 129 | DocProject/Help/*.hhk 130 | DocProject/Help/*.hhp 131 | DocProject/Help/Html2 132 | DocProject/Help/html 133 | 134 | # Click-Once directory 135 | publish/ 136 | 137 | # Publish Web Output 138 | *.[Pp]ublish.xml 139 | *.azurePubxml 140 | # TODO: Comment the next line if you want to checkin your web deploy settings 141 | # but database connection strings (with potential passwords) will be unencrypted 142 | *.pubxml 143 | *.publishproj 144 | 145 | # NuGet Packages 146 | *.nupkg 147 | # The packages folder can be ignored because of Package Restore 148 | **/packages/* 149 | # except build/, which is used as an MSBuild target. 150 | !**/packages/build/ 151 | # Uncomment if necessary however generally it will be regenerated when needed 152 | #!**/packages/repositories.config 153 | # NuGet v3's project.json files produces more ignoreable files 154 | *.nuget.props 155 | *.nuget.targets 156 | 157 | # Microsoft Azure Build Output 158 | csx/ 159 | *.build.csdef 160 | 161 | # Microsoft Azure Emulator 162 | ecf/ 163 | rcf/ 164 | 165 | # Microsoft Azure ApplicationInsights config file 166 | ApplicationInsights.config 167 | 168 | # Windows Store app package directory 169 | AppPackages/ 170 | BundleArtifacts/ 171 | 172 | # Visual Studio cache files 173 | # files ending in .cache can be ignored 174 | *.[Cc]ache 175 | # but keep track of directories ending in .cache 176 | !*.[Cc]ache/ 177 | 178 | # Others 179 | ClientBin/ 180 | ~$* 181 | *~ 182 | *.dbmdl 183 | *.dbproj.schemaview 184 | *.pfx 185 | *.publishsettings 186 | node_modules/ 187 | orleans.codegen.cs 188 | 189 | # RIA/Silverlight projects 190 | Generated_Code/ 191 | 192 | # Backup & report files from converting an old project file 193 | # to a newer Visual Studio version. Backup files are not needed, 194 | # because we have git ;-) 195 | _UpgradeReport_Files/ 196 | Backup*/ 197 | UpgradeLog*.XML 198 | UpgradeLog*.htm 199 | 200 | # SQL Server files 201 | *.mdf 202 | *.ldf 203 | 204 | # Business Intelligence projects 205 | *.rdl.data 206 | *.bim.layout 207 | *.bim_*.settings 208 | 209 | # Microsoft Fakes 210 | FakesAssemblies/ 211 | 212 | # GhostDoc plugin setting file 213 | *.GhostDoc.xml 214 | 215 | # Node.js Tools for Visual Studio 216 | .ntvs_analysis.dat 217 | 218 | # Visual Studio 6 build log 219 | *.plg 220 | 221 | # Visual Studio 6 workspace options file 222 | *.opt 223 | 224 | # Visual Studio LightSwitch build output 225 | **/*.HTMLClient/GeneratedArtifacts 226 | **/*.DesktopClient/GeneratedArtifacts 227 | **/*.DesktopClient/ModelManifest.xml 228 | **/*.Server/GeneratedArtifacts 229 | **/*.Server/ModelManifest.xml 230 | _Pvt_Extensions 231 | 232 | # Paket dependency manager 233 | .paket/paket.exe 234 | 235 | # FAKE - F# Make 236 | .fake/ 237 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 dday9 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # .NET-JSON-Transformer 3 | A Visual Basic .NET (VB.NET) implementation that converts a JSON literal into a .NET XDocument 4 | 5 | ## Add to project 6 | The [json.vb](json.vb) code file is uncompiled. Follow these instructions to add the file to your project: 7 | 8 | 1. Project > Add Existing Item (shift + alt + a) 9 | 2. Select the file in the 10 | browse dialog 11 | 3. Add 12 | 13 | ## Json.Parse Method 14 | Creates a new [XDocument](https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument) from a JSON literal 15 | 16 | ``` vb 17 | Public Shared Function Parse(ByVal source As String, Optional culuture As CultureInfo) As XDocument 18 | ``` 19 | 20 | ### Parameters 21 | `source` [String](https://docs.microsoft.com/en-us/dotnet/api/system.string) 22 | A string that contains JSON. 23 | 24 | `culture` 25 | And optional parameter to specify the culture. This is used for culture specific parsing (e.g. commas used as decimal place) 26 | 27 | ### Returns 28 | [XDocument](https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument) 29 | An [XDocument](https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument) populated from the string that contains JSON. 30 | 31 | ### Example 32 | The following example illustrates the Json.Parse method: 33 | ``` vb 34 | Const literal = "{ ""Property1"": 1, ""Property2"": false }" 35 | Dim parsedJson = Json.Parse(literal) 36 | Console.WriteLine(parsedJson.ToString()) 37 | 38 | ' 39 | ' 40 | ' Property1 41 | ' 42 | ' 1 43 | ' 44 | ' 45 | ' 46 | ' Property2 47 | ' 48 | ' false 49 | ' 50 | ' 51 | ' 52 | ``` 53 | 54 | ## Remarks 55 | * The parser ignores whitespace, essentially minfying the JSON. For example, if the JSON literal is: 56 | ``` json 57 | { 58 | "glossary": { 59 | "title": "example glossary", 60 | "GlossDiv": { 61 | "title": "S", 62 | "GlossList": { 63 | "GlossEntry": { 64 | "ID": "SGML", 65 | "SortAs": "SGML", 66 | "GlossTerm": "Standard Generalized Markup Language", 67 | "Acronym": "SGML", 68 | "Abbrev": "ISO 8879:1986", 69 | "GlossDef": { 70 | "para": "A meta-markup language, used to create markup languages such as DocBook.", 71 | "GlossSeeAlso": ["GML", "XML"] 72 | }, 73 | "GlossSee": "markup" 74 | } 75 | } 76 | } 77 | } 78 | } 79 | ``` 80 | Then it gets parsed as: 81 | ``` json 82 | {"glossary":{"title":"example glossary","GlossDiv":{"title":"S","GlossList":{"GlossEntry":{"ID":"SGML","SortAs":"SGML","GlossTerm":"Standard Generalized Markup Language","Acronym":"SGML","Abbrev":"ISO 8879:1986","GlossDef":{"para":"A meta-markup language, used to create markup languages such as DocBook.","GlossSeeAlso":["GML","XML"]},"GlossSee":"markup"}}}}} 83 | ``` 84 | 85 | * The returned XML is a 1-to-1 translation from the JSON. Using the same example as above, the resulting XML would be: 86 | ``` 87 | 88 | 89 | glossary 90 | 91 | 92 | 93 | title 94 | 95 | example glossary 96 | 97 | 98 | 99 | GlossDiv 100 | 101 | 102 | 103 | title 104 | 105 | S 106 | 107 | 108 | 109 | GlossList 110 | 111 | 112 | 113 | GlossEntry 114 | 115 | 116 | 117 | ID 118 | 119 | SGML 120 | 121 | 122 | 123 | SortAs 124 | 125 | SGML 126 | 127 | 128 | 129 | GlossTerm 130 | 131 | Standard Generalized Markup Language 132 | 133 | 134 | 135 | Acronym 136 | 137 | SGML 138 | 139 | 140 | 141 | Abbrev 142 | 143 | ISO 8879:1986 144 | 145 | 146 | 147 | GlossDef 148 | 149 | 150 | 151 | para 152 | 153 | A meta-markup language, used to create markup languages such as DocBook. 154 | 155 | 156 | 157 | GlossSeeAlso 158 | 159 | 160 | GML 161 | XML 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | GlossSee 170 | 171 | markup 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | ``` 188 | 189 | * The parser does not parse to the exact specifications of the EBNF found on http://www.json.org/ the following list the deviations in this parser: 190 | * Number: Checks if the number starts with either a positive sign or negative sign 191 | * Boolean: Checks for "true" or "false" based on case-insensitivity 192 | * Null: Checks for "null" based on case-insensitivity 193 | * String: Does not check for "\u" followed by 4 hexadecimal characters as an escape character 194 | 195 | ## Examples and Demo 196 | The following example demonstrates the Parse method. 197 | 198 | ``` vb.net 199 | Public Module Module1 200 | 201 | Public Sub Main() 202 | Const literal = "{ 203 | ""glossary"": { 204 | ""title"": ""example glossary"", 205 | ""GlossDiv"": { 206 | ""title"": ""S"", 207 | ""GlossList"": { 208 | ""GlossEntry"": { 209 | ""ID"": ""SGML"", 210 | ""SortAs"": ""SGML"", 211 | ""GlossTerm"": ""Standard Generalized Markup Language"", 212 | ""Acronym"": ""SGML"", 213 | ""Abbrev"": ""ISO 8879:1986"", 214 | ""GlossDef"": { 215 | ""para"": ""A meta-markup language, used to create markup languages such as DocBook."", 216 | ""GlossSeeAlso"": [""GML"", ""XML""] 217 | }, 218 | ""GlossSee"": ""markup"" 219 | } 220 | } 221 | } 222 | } 223 | }" 224 | Dim parsedJson = Json.Parse(literal) 225 | Console.WriteLine(parsedJson.ToString()) 226 | Console.ReadLine() 227 | End Sub 228 | 229 | End Module 230 | ``` 231 | Fiddle: [https://dotnetfiddle.net/FqqPnW](https://dotnetfiddle.net/1YUHJo) 232 | 233 | ## Donate 234 | Show your support! Your (non-tax deductible) donation of Monero cryptocurrency is a sign of solidarity among web developers. 235 | 236 | Being self taught, I have come a long way over the years. I certainly do not intend on making a living from this free feature, but my hope is to earn a few dollars to validate all of my hard work. 237 | 238 | Monero Address: 447SPi8XcexZnF7kYGDboKB6mghWQzRfyScCgDP2r4f2JJTfLGeVcFpKEBT9jazYuW2YG4qn51oLwXpQJ3oEXkeXUsd6TCF 239 | 240 | ![447SPi8XcexZnF7kYGDboKB6mghWQzRfyScCgDP2r4f2JJTfLGeVcFpKEBT9jazYuW2YG4qn51oLwXpQJ3oEXkeXUsd6TCF](monero.png) 241 | -------------------------------------------------------------------------------- /json.vb: -------------------------------------------------------------------------------- 1 | Imports System.Globalization 2 | 3 | Public Class Json 4 | 5 | Public Const COLON As Char = ":"c 6 | Public Const COMMA As Char = ","c 7 | Public Const CLOSE_BRACE As Char = "}"c 8 | Public Const CLOSE_BRACKET As Char = "]"c 9 | Public Const DOUBLE_QUOTE As Char = """"c 10 | Public Const ESCAPED_CHARACTERS As String = DOUBLE_QUOTE & "\/bfnrt" 11 | Public Const NULL_LITERAL As String = "null" 12 | Public Const OPEN_BRACE As Char = "{"c 13 | Public Const OPEN_BRACKET As Char = "["c 14 | 15 | ''' 16 | ''' Creates a new from a JSON literal 17 | ''' 18 | ''' A string that contains JSON. 19 | ''' 20 | ''' An populated from the string that contains JSON. 21 | Public Shared Function Parse(source As String, Optional culture As CultureInfo = Nothing) As XDocument 22 | 'Remove any whitespace 23 | source = source.Trim() 24 | If String.IsNullOrWhiteSpace(source) Then 25 | Return Nothing 26 | End If 27 | 28 | Dim value = ParseValue(source, 0, culture) 29 | Dim document = If(value IsNot Nothing, New XDocument(New XDeclaration("1.0", "utf-8", "yes"), value), Nothing) 30 | 31 | Return document 32 | End Function 33 | 34 | ''' 35 | ''' Creates a new from a JSON literal at given index 36 | ''' 37 | ''' A string that contains JSON. 38 | ''' The position of the JSON where the parsing will begin. 39 | ''' An populated from the string that contains JSON. 40 | ''' 1. will increment if the parse is successful. 41 | ''' 2. Nothing will be returned if the parse is not successful. 42 | Private Shared Function ParseValue(source As String, ByRef index As Integer, Optional culture As CultureInfo = Nothing) As XElement 43 | 'Declare a temporary placeholder and skip any whitespace 44 | Dim tempIndex = SkipWhitespace(source, index) 45 | 46 | 'Go through each available value until one returns something that isn't null 47 | Dim value = ParseObject(source, tempIndex, culture) 48 | If (value Is Nothing) Then 49 | value = ParseArray(source, tempIndex, culture) 50 | If (value Is Nothing) Then 51 | value = ParseString(source, tempIndex) 52 | If (value Is Nothing) Then 53 | value = ParseNumber(source, tempIndex, culture) 54 | If (value Is Nothing) Then 55 | value = ParseBoolean(source, tempIndex) 56 | If (value Is Nothing) Then 57 | value = ParseNull(source, tempIndex) 58 | End If 59 | End If 60 | End If 61 | End If 62 | End If 63 | 64 | 'Change the index 65 | index = tempIndex 66 | 67 | 'Return the value 68 | Return value 69 | End Function 70 | 71 | ''' 72 | ''' Creates a new from a JSON literal at given index where the expected JSON type is an Object 73 | ''' 74 | ''' A string that contains JSON. 75 | ''' The position of the JSON where the parsing will begin. 76 | ''' An populated from the string that contains JSON. 77 | ''' 1. will increment if the parse is successful. 78 | ''' 2. Nothing will be returned if the parse is not successful. 79 | Private Shared Function ParseObject(source As String, ByRef index As Integer, Optional culture As CultureInfo = Nothing) As XElement 80 | 'Declare a value to return 81 | Dim value As XElement = Nothing 82 | 83 | 'Declare a temporary placeholder 84 | Dim tempIndex = index 85 | 86 | 'Check for the starting opening curly bracket 87 | If source(tempIndex).Equals(OPEN_BRACE) Then 88 | 'Increment the index 89 | tempIndex += 1 90 | 91 | 'Declare a collection that will make up the nodes' value 92 | Dim nodes = New List(Of Tuple(Of String, XElement)) 93 | 94 | 'Declare an XElement to store the key (aka - name) of the KeyValuePair 95 | Dim key As XElement 96 | 97 | 'Declare an XElement which will represent the value of the KeyValuePair 98 | Dim item As XElement 99 | 100 | 'Loop until we've reached the end of the source or until we've hit the ending bracket 101 | Do While (tempIndex < source.Length AndAlso Not source(tempIndex).Equals(CLOSE_BRACE)) 102 | 'Increment the index and skip any unneeded whitespace 103 | tempIndex = SkipWhitespace(source, tempIndex) 104 | 105 | 'Attempt to parse the String 106 | key = ParseString(source, tempIndex) 107 | 108 | 'Check if the parse was successful 109 | If (key Is Nothing) Then 110 | Throw New Exception($"Expected a String instead of a '{source(tempIndex)}' at position: {tempIndex}.") 111 | Else 112 | 'Skip any unneeded whitespace 113 | tempIndex = SkipWhitespace(source, tempIndex) 114 | 115 | If (tempIndex < source.Length) Then 116 | 'Check if the currently iterated character is a object separator ':' 117 | If (source(tempIndex) = COLON) Then 118 | 'Increment the index and skip any unneeded whitespace 119 | tempIndex = SkipWhitespace(source, tempIndex + 1) 120 | 121 | If (tempIndex < source.Length) Then 122 | 'Assign the item to the parsed value 123 | item = ParseValue(source, tempIndex, culture) 124 | 125 | 'Check if the parse was successful 126 | If (item Is Nothing) Then 127 | Throw New Exception($"Unexpected character '{source(tempIndex)}' at position: {tempIndex}.") 128 | Else 129 | 'Add the item to the collection 130 | nodes.Add(New Tuple(Of String, XElement)(key.Value, item)) 131 | 132 | 'Skip any unneeded whitespace 133 | tempIndex = SkipWhitespace(source, tempIndex) 134 | 135 | 'Check if we can continue 136 | If (tempIndex < source.Length) Then 137 | 'Check if the currently iterated character is either a item separator (comma) or ending curly bracket 138 | If (source(tempIndex).Equals(COMMA)) Then 139 | 'Increment the index and skip any unneeded whitespace 140 | tempIndex = SkipWhitespace(source, tempIndex + 1) 141 | ElseIf (source(tempIndex) <> CLOSE_BRACE) Then 142 | Throw New Exception($"Expected a ',' instead of a '{source(tempIndex)}' at position: {tempIndex}.") 143 | End If 144 | End If 145 | End If 146 | Else 147 | Throw New Exception("Expected an Object, Array, String, Number, Boolean, or Null instead I reached the end of the source code.") 148 | End If 149 | Else 150 | Throw New Exception($"Expected a ':' instead of a '{source(tempIndex)}' at position: {tempIndex}.") 151 | End If 152 | Else 153 | Throw New Exception("Expected a ',' instead I reached the end of the source code.") 154 | End If 155 | End If 156 | Loop 157 | 158 | 'Check if the currently iterated value is an ending curly bracket 159 | If (tempIndex < source.Length AndAlso source(tempIndex) = CLOSE_BRACE) Then 160 | 'Increment the index 161 | tempIndex += 1 162 | 163 | 'Set the new index 164 | index = tempIndex 165 | 166 | 'Create the Object 167 | value = New XElement("object") 168 | 169 | 'Iterate through each item in the nodes 170 | Dim objectItem, objectKey, objectValue As XElement 171 | For Each n As Tuple(Of String, XElement) In nodes 172 | objectKey = New XElement("key", n.Item1) 173 | objectValue = New XElement("value", n.Item2) 174 | 175 | objectItem = New XElement("item", {objectKey, objectValue}) 176 | value.Add(objectItem) 177 | Next 178 | End If 179 | End If 180 | 181 | 'Return the value 182 | Return value 183 | End Function 184 | 185 | ''' 186 | ''' Creates a new from a JSON literal at given index where the expected JSON type is an array 187 | ''' 188 | ''' A string that contains JSON. 189 | ''' The position of the JSON where the parsing will begin. 190 | ''' An populated from the string that contains JSON. 191 | ''' 1. will increment if the parse is successful. 192 | ''' 2. Nothing will be returned if the parse is not successful. 193 | Private Shared Function ParseArray(source As String, ByRef index As Integer, Optional culture As CultureInfo = Nothing) As XElement 194 | 'Declare a value to return 195 | Dim value As XElement = Nothing 196 | 197 | 'Declare a temporary placeholder 198 | Dim tempIndex As Integer = index 199 | 200 | 'Check for the starting opening bracket 201 | If (source(tempIndex).Equals(OPEN_BRACKET)) Then 202 | 'Increment the index 203 | tempIndex += 1 204 | 205 | 'Declare a collection that will make up the nodes' value 206 | Dim nodes = New List(Of XElement) 207 | 208 | 'Declare an XElement which will represent the currently iterated item in the array 209 | Dim item As XElement 210 | 211 | 'Loop until we've reached the end of the source or until we've hit the ending bracket 212 | Do While (tempIndex < source.Length AndAlso Not source(tempIndex).Equals(CLOSE_BRACKET)) 213 | 'Assign the item to the parsed value 214 | item = ParseValue(source, tempIndex, culture) 215 | 216 | 'Check if the parse was successful 217 | If (item Is Nothing) Then 218 | Throw New Exception($"Unexpected character '{source(tempIndex)}' at position: {tempIndex}.") 219 | Else 220 | 'Add the item to the collection 221 | nodes.Add(item) 222 | 223 | 'Skip any unneeded whitespace 224 | tempIndex = SkipWhitespace(source, tempIndex) 225 | 226 | 'Check if we can continue 227 | If (tempIndex < source.Length) Then 228 | 'Check if the currently iterated character is either a item separator (comma) or ending bracket 229 | If (source(tempIndex).Equals(COMMA)) Then 230 | 'Increment the index and skip any unneeded whitespace 231 | tempIndex = SkipWhitespace(source, tempIndex + 1) 232 | ElseIf (source(tempIndex) <> CLOSE_BRACKET) Then 233 | Throw New Exception($"Expected a ',' instead of a '{source(tempIndex)}' at position: {tempIndex}.") 234 | End If 235 | End If 236 | End If 237 | Loop 238 | 239 | 'Check if the currently iterated value is an ending bracket 240 | If (tempIndex < source.Length AndAlso source(tempIndex) = CLOSE_BRACKET) Then 241 | 'Increment the index 242 | tempIndex += 1 243 | 244 | 'Set the new index 245 | index = tempIndex 246 | 247 | 'Create the Array 248 | value = New XElement("array", nodes) 249 | End If 250 | End If 251 | 252 | Return value 253 | End Function 254 | 255 | ''' 256 | ''' Creates a new from a JSON literal at given index where the expected JSON type is a String 257 | ''' 258 | ''' A string that contains JSON. 259 | ''' The position of the JSON where the parsing will begin. 260 | ''' An populated from the string that contains JSON. 261 | ''' 1. will increment if the parse is successful. 262 | ''' 2. Nothing will be returned if the parse is not successful. 263 | ''' 3. The parser deviates from ECMA-404 by not checking for "\u" followed by 4 hexadecimal characters as an escape character 264 | Private Shared Function ParseString(source As String, ByRef index As Integer) As XElement 265 | 'Declare a value to return 266 | Dim value As XElement = Nothing 267 | 268 | 'Declare a temporary placeholder 269 | Dim tempIndex As Integer = index 270 | 271 | 'Check for the starting double-quote 272 | If (source(tempIndex).Equals(DOUBLE_QUOTE)) Then 273 | 'Increment the index 274 | tempIndex += 1 275 | 276 | 'Loop until we've reached the end of the source or until we've hit the ending double-quote 277 | Do While (tempIndex < source.Length AndAlso Not source(tempIndex).Equals(DOUBLE_QUOTE)) 278 | 'Check if we're at an escaped character 279 | If (source(tempIndex) = "\"c AndAlso 280 | tempIndex + 1 < source.Length AndAlso 281 | ESCAPED_CHARACTERS.IndexOf(source(tempIndex + 1)) <> -1) Then 282 | tempIndex += 1 283 | ElseIf (source(tempIndex) = "\"c) Then 284 | Throw New Exception("Unescaped backslash in a String. Position: " & index) 285 | End If 286 | 287 | 'Increment the index 288 | tempIndex += 1 289 | Loop 290 | 291 | 'Check if the currently iterated character is a double-quote 292 | If (tempIndex < source.Length AndAlso source(tempIndex).Equals(DOUBLE_QUOTE)) Then 293 | 'Increment the index 294 | tempIndex += 1 295 | 296 | 'Create the String 297 | value = New XElement("string", source.Substring(index + 1, tempIndex - index - 2)) 298 | 299 | 'Set the new index 300 | index = tempIndex 301 | End If 302 | End If 303 | 304 | Return value 305 | End Function 306 | 307 | ''' 308 | ''' Creates a new from a JSON literal at given index where the expected JSON type is a number 309 | ''' 310 | ''' A string that contains JSON. 311 | ''' The position of the JSON where the parsing will begin. 312 | ''' An populated from the string that contains JSON. 313 | ''' 1. will increment if the parse is successful. 314 | ''' 2. Nothing will be returned if the parse is not successful. 315 | ''' 3. The parser deviates from ECMA-404 by checking for an optional unary positive sign operator 316 | Private Shared Function ParseNumber(source As String, ByRef index As Integer, Optional culture As CultureInfo = Nothing) As XElement 317 | 'Get the current culture information 318 | If (culture Is Nothing) Then 319 | culture = CultureInfo.CurrentCulture 320 | End If 321 | 322 | 'Declare a temporary placeholder 323 | Dim tempIndex = index 324 | 325 | 'Check for the optional unary operator 326 | If (source.IndexOf(culture.NumberFormat.NegativeSign, tempIndex, StringComparison.OrdinalIgnoreCase) = tempIndex OrElse 327 | source.IndexOf(culture.NumberFormat.PositiveSign, tempIndex, StringComparison.OrdinalIgnoreCase) = tempIndex) Then 328 | tempIndex += 1 329 | End If 330 | 331 | 'Match one or more digits 332 | If (tempIndex < source.Length AndAlso Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex).ToString()) <> -1) Then 333 | Do While (tempIndex < source.Length AndAlso Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex).ToString()) <> -1) 334 | tempIndex += 1 335 | Loop 336 | Else 337 | Return Nothing 338 | End If 339 | 340 | 'Optionally match a decimal separator followed by one or more digits 341 | If (tempIndex + 1 < source.Length AndAlso 342 | source.IndexOf(culture.NumberFormat.NumberDecimalSeparator, tempIndex, StringComparison.OrdinalIgnoreCase) = tempIndex AndAlso 343 | Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex + 1).ToString()) <> -1) Then 344 | 345 | tempIndex += 1 346 | Do While (tempIndex < source.Length AndAlso Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex).ToString()) <> -1) 347 | tempIndex += 1 348 | Loop 349 | End If 350 | 351 | 'Optionally match an exponent, followed by an optional unary operator, followed by 1 or more digits 352 | If (tempIndex + 1 < source.Length AndAlso 353 | source.IndexOf("e", tempIndex, StringComparison.OrdinalIgnoreCase) = tempIndex) Then 354 | 355 | If (tempIndex + 2 < source.Length) AndAlso 356 | (source.IndexOf(culture.NumberFormat.NegativeSign, tempIndex + 1, StringComparison.OrdinalIgnoreCase) = tempIndex + 1 OrElse 357 | source.IndexOf(culture.NumberFormat.PositiveSign, tempIndex + 1, StringComparison.OrdinalIgnoreCase) = tempIndex + 1) AndAlso 358 | Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex + 2).ToString()) <> -1 Then 359 | tempIndex += 2 360 | ElseIf (tempIndex + 1 < source.Length AndAlso Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex + 1).ToString()) <> -1) Then 361 | tempIndex += 1 362 | End If 363 | 364 | Do While (tempIndex < source.Length AndAlso Array.IndexOf(culture.NumberFormat.NativeDigits, source(tempIndex).ToString()) <> -1) 365 | tempIndex += 1 366 | Loop 367 | End If 368 | 369 | 'Create the number 370 | Dim value = New XElement("number", source.Substring(index, tempIndex - index)) 371 | index = tempIndex 372 | 373 | Return value 374 | End Function 375 | 376 | ''' 377 | ''' Creates a new from a JSON literal at given index where the expected JSON type is a boolean 378 | ''' 379 | ''' A string that contains JSON. 380 | ''' The position of the JSON where the parsing will begin. 381 | ''' An populated from the string that contains JSON. 382 | ''' 1. will increment if the parse is successful. 383 | ''' 2. Nothing will be returned if the parse is not successful. 384 | ''' 3. The parser deviates from ECMA-404 by ignoring the casing 385 | Private Shared Function ParseBoolean(source As String, ByRef index As Integer) As XElement 386 | Dim value As XElement = Nothing 387 | 388 | 'Literally match 'true' or 'false' 389 | If source.IndexOf(Boolean.TrueString, index, StringComparison.OrdinalIgnoreCase) = index Then 390 | value = New XElement("boolean", True) 391 | index += 4 392 | ElseIf source.IndexOf(Boolean.FalseString, index, StringComparison.OrdinalIgnoreCase) = index Then 393 | value = New XElement("boolean", False) 394 | index += 5 395 | End If 396 | 397 | Return value 398 | End Function 399 | 400 | ''' 401 | ''' Creates a new from a JSON literal at given index where the expected JSON type is the literal "null" 402 | ''' 403 | ''' A string that contains JSON. 404 | ''' The position of the JSON where the parsing will begin. 405 | ''' An populated from the string that contains JSON. 406 | ''' 1. will increment if the parse is successful. 407 | ''' 2. Nothing will be returned if the parse is not successful. 408 | ''' 3. The parser deviates from ECMA-404 by ignoring the casing 409 | Private Shared Function ParseNull(source As String, ByRef index As Integer) As XElement 410 | Dim value As XElement = Nothing 411 | 412 | 'Literally match 'null' in the source starting at the index 413 | If source.IndexOf(NULL_LITERAL, index, StringComparison.OrdinalIgnoreCase) = index Then 414 | value = New XElement(NULL_LITERAL) 415 | index += 4 416 | End If 417 | 418 | Return value 419 | End Function 420 | 421 | ''' 422 | ''' Starting at a given index, skip any character that is whitespace 423 | ''' 424 | ''' A string that contains JSON. 425 | ''' The position of the JSON where the whitespace check will begin 426 | ''' An Integer where the first character of , starting at , is not whitespace. 427 | Private Shared Function SkipWhitespace(source As String, ByRef index As Integer) As Integer 428 | Do While (index < source.Length AndAlso Char.IsWhiteSpace(source(index))) 429 | index += 1 430 | Loop 431 | 432 | Return index 433 | End Function 434 | 435 | End Class 436 | 437 | -------------------------------------------------------------------------------- /monero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dday9/.NET-JSON-Transformer/4fb4722a73ccd5d73e66716351a55c62f5cc666d/monero.png --------------------------------------------------------------------------------