- VBNet 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| Resources/ICON | ||
| Sources | ||
| .gitattributes | ||
| .gitignore | ||
| CHANGELOG.md | ||
| LICENSE | ||
| README.md | ||
| Settings | ||
tsJson
JSON for twinBASIC that never lies to you about what's in the tree.
One data model with six types: objects and arrays you pass around like real values instead of fragile parse trees, and every failure comes back as a JsonError you can inspect on your own terms.
Features
- Parse never throws. Broken input gives you
Emptyand aJsonErrorwith a code, a line, a column, and a snippet of the offending text - never an exception unwinding your call stack. - One address for the whole package. JSONPath hits, Patch targets, Schema error locations - all RFC 6901 pointers. Learn it once, use it everywhere.
- Patch either wins completely or changes nothing. It runs on a copy, so a failure halfway through never leaves your document half-mutated - and your original stays untouched either way.
- Six types, not seven.
Datenever sneaks into the tree as a silent guess.Parsenever invents one, andSerializeonly ever writes the one you put there yourself. - One JsonError shape for every failure. Parse, IO, pointer, path syntax, patch, schema runtime - 26 documented codes, read the same way wherever they come from.
- Standards, not reinventions. RFC 6901 (Pointer), RFC 9535 (JSONPath, functions included), RFC 6902 + 7396 (Patch and Merge Patch), JSON Schema 2020-12 with a real
$refcatalog, RFC 9485 (I-Regexp) - one twinPack, the whole stack.
Examples
Simple
Text in, text out. Parse reads JSON, Serialize writes it back - that is the whole default path:
Dim v As Variant = Json.Parse("{""title"":""A"",""price"":8}")
If JsonError.HasError Then
Debug.Print JsonError.ToString()
Exit Sub
End If
Debug.Print Json.Serialize(v)
' {
' "title": "A",
' "price": 8
' }
A little more
A catalog that starts out with no JSON text at all, then grows by one book that arrives as text, and ends with exactly one spot corrected - without root ever finding out:
' Step 1: build the tree with no JSON text at all - Map/Arr are valid JSON right away
Dim books As JsonArray = Json.Arr(Json.Map("title", "A", "price", 8))
Dim root As JsonObject = Json.Map("store", Json.Map("book", books))
' Step 2: a second book arrives as JSON text (e.g. from a file) ...
Dim incoming As Variant = Json.Parse("{""title"":""B"",""price"":12}")
If JsonError.HasError Then
Debug.Print JsonError.ToString()
Exit Sub
End If
' ... and lands in the very books array Step 1 built - in-place, root grows along with it
books.Add incoming
Debug.Print Json.Serialize(root, JsonOptions.Minified)
' {"store":{"book":[{"title":"A","price":8},{"title":"B","price":12}]}}
' Step 3: find the cheap book in this root via JSONPath, instead of searching by hand
Dim hit As JsonHit
If Not Json.TryQueryOneHit(root, "$.store.book[?@.price<10]", hit) Then Exit Sub
Debug.Print hit.Pointer ' /store/book/0
' Step 4: reuse the pointer from that exact hit - first to read ...
Debug.Print Json.PointerGet(root, hit.Pointer & "/title") ' A
' ... then for the patch, so it's guaranteed to hit the same spot, on a copy
Dim ops As JsonArray = Json.Arr(Json.Map("op", "replace", "path", hit.Pointer & "/price", "value", 9))
Dim patched As Variant = Json.Patch(root, ops)
' Step 5: root from the first two steps still shows the old price -
' patched is the only value that changed
Debug.Print Json.Serialize(patched, JsonOptions.Minified)
' {"store":{"book":[{"title":"A","price":9},{"title":"B","price":12}]}}
Several tools, one tree, no translation in between: Map and Arr build it from plain code, Parse reads it from text - a literal in this example, just as well the contents of a file or a response. Once both sit in the same root, there's no difference left between what you built by hand and what came from outside. JSONPath searches it without you climbing through objects and arrays yourself, and it brings the address along: the same pointer you read with is also the pointer Patch then uses to hit that exact value - on a copy, root stays as it was. Schema validation against 2020-12 with a $ref catalog, JSONC comments, and JSONL hang off the same model; they just don't happen to show up in this example.
Installation
Inside the twinBASIC IDE: Project -> References... (Ctrl+T) -> tab Available Packages
Search there for tsJson -> check it -> Apply Changes.
The package is MIT-licensed, and the name carries the initials ts of its developer, Thomas Stiller.
The repository lives on a home server: git.thosti.dev/th.stiller/tsJson. The first request can take a little longer.
Documentation | Back to the top
tsJson (Deutsch)
JSON für twinBASIC, das dir nie etwas über den Baum vormacht.
Ein Datenmodell mit sechs Typen: Objekte und Arrays, die du wie echte Werte durchreichst statt wie zerbrechliche Parse-Bäume, und jeder Fehlschlag kommt als JsonError zurück, den du in Ruhe auswerten kannst.
Merkmale
- Parse wirft nie. Kaputte Eingabe ergibt
Emptyund einJsonErrormit Code, Zeile, Spalte und einem Snippet der betroffenen Stelle - nie eine Exception, die deinen Call-Stack aufreißt. - Eine Adresse für das ganze Paket. JSONPath-Treffer, Patch-Ziele, Schema-Fehlerorte - alles RFC-6901-Pointer. Einmal lernen, überall anwenden.
- Ein Patch gewinnt vollständig oder ändert gar nichts. Er arbeitet auf einer Kopie, deshalb bleibt ein auf halbem Weg gescheiterter Patch nie halb angewendet zurück - und dein Original bleibt so oder so unberührt.
- Sechs Typen, nicht sieben.
Dateschleicht sich nie als stille Vermutung in den Baum.Parseerfindet nie eines, undSerializeschreibt nur das, das du selbst hineingelegt hast. - Ein JsonError-Format für jeden Fehlerfall. Parse, IO, Pointer, Path-Syntax, Patch, Schema-Laufzeit - 26 dokumentierte Codes, überall gleich zu lesen.
- Standards statt Neuerfindungen. RFC 6901 (Pointer), RFC 9535 (JSONPath, Funktionen eingeschlossen), RFC 6902 + 7396 (Patch und Merge Patch), JSON Schema 2020-12 mit echtem
$ref-Katalog, RFC 9485 (I-Regexp) - ein twinPack, der ganze Stack.
Beispiele
Einfach
Text rein, Text raus. Parse liest JSON, Serialize schreibt es wieder - das ist der ganze Default-Weg:
Dim v As Variant = Json.Parse("{""title"":""A"",""price"":8}")
If JsonError.HasError Then
Debug.Print JsonError.ToString()
Exit Sub
End If
Debug.Print Json.Serialize(v)
' {
' "title": "A",
' "price": 8
' }
Etwas mehr
Ein Katalog, der zuerst ganz ohne JSON-Text entsteht, dann um ein Buch wächst, das als Text hereinkommt, und am Ende an genau einer Stelle korrigiert wird - ohne dass root davon etwas mitbekommt:
' Schritt 1: Baum bauen, ganz ohne JSON-Text - Map/Arr sind sofort gültiges JSON
Dim books As JsonArray = Json.Arr(Json.Map("title", "A", "price", 8))
Dim root As JsonObject = Json.Map("store", Json.Map("book", books))
' Schritt 2: ein zweites Buch kommt als JSON-Text herein (z.B. aus einer Datei) ...
Dim incoming As Variant = Json.Parse("{""title"":""B"",""price"":12}")
If JsonError.HasError Then
Debug.Print JsonError.ToString()
Exit Sub
End If
' ... und wandert in genau das books-Array, das Schritt 1 gebaut hat - in-place, root wächst mit
books.Add incoming
Debug.Print Json.Serialize(root, JsonOptions.Minified)
' {"store":{"book":[{"title":"A","price":8},{"title":"B","price":12}]}}
' Schritt 3: in diesem root das billige Buch per JSONPath finden, statt von Hand zu suchen
Dim hit As JsonHit
If Not Json.TryQueryOneHit(root, "$.store.book[?@.price<10]", hit) Then Exit Sub
Debug.Print hit.Pointer ' /store/book/0
' Schritt 4: den Pointer aus genau diesem Treffer weiterverwenden - erst lesend ...
Debug.Print Json.PointerGet(root, hit.Pointer & "/title") ' A
' ... dann für den Patch, damit garantiert dieselbe Stelle getroffen wird, auf einer Kopie
Dim ops As JsonArray = Json.Arr(Json.Map("op", "replace", "path", hit.Pointer & "/price", "value", 9))
Dim patched As Variant = Json.Patch(root, ops)
' Schritt 5: root aus den ersten beiden Schritten zeigt weiterhin den alten Preis -
' patched ist der einzige Wert, der sich geändert hat
Debug.Print Json.Serialize(patched, JsonOptions.Minified)
' {"store":{"book":[{"title":"A","price":9},{"title":"B","price":12}]}}
Mehrere Werkzeuge an einem Baum, keine Übersetzung dazwischen: Map und Arr bauen ihn aus purem Code, Parse liest ihn aus Text - im Beispiel ein Literal, genauso gut der Inhalt einer Datei oder Antwort. Sobald beides im selben root sitzt, gibt es keinen Unterschied mehr zwischen dem, was du von Hand gebaut hast, und dem, was von außen kam. JSONPath sucht darin, ohne dass du selbst durch Objekte und Arrays hangelst, und bringt die Adresse gleich mit: derselbe Pointer, mit dem du liest, ist auch der Pointer, mit dem Patch danach genau diesen einen Wert trifft - auf einer Kopie, root bleibt, wie es war. Schema-Prüfung nach 2020-12 mit $ref-Katalog, JSONC-Kommentare und JSONL hängen am selben Modell, sie kommen in diesem Beispiel nur zufällig nicht vor.
Installation
In der twinBASIC-IDE: Projekt -> Verweise... (Strg+T) -> Reiter Available Packages
Dort nach tsJson suchen -> Häkchen setzen -> Apply Changes.
Das Paket ist MIT-lizenziert und der Name trägt die Initialen ts des Entwicklers Thomas Stiller.
Das Repository liegt auf einem Homeserver: git.thosti.dev/th.stiller/tsJson. Der erste Aufruf kann etwas länger dauern.