Commit 5e7177c7 authored by Kyle Clarke's avatar Kyle Clarke 💬

Initial commit of code in private repo, now made public =]

parents
# IntelliJ project files
.idea
*.iml
# Private folders
vendor/
etc/*
cindy
.DS_Store
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# WhatJSON?
___
A simple util written in Go that parses a request body (of type []bytes) and returns whether the body is a JSON Object or Array type. Handy when consuming JSON api responses and you're not sure whether the resource returns a collection/array or a single record/object. Will return `unknown` if unable to match.
If the resource has followed the [http://jsonapi.org/format/](http://jsonapi.org/format/) spec, then a single entity should be an object, a collection an array. [Reference](http://jsonapi.org/format/#document-top-level)
```
Primary data MUST be either:
* a single resource object, a single resource identifier object, or null, for requests that target single resources
* an array of resource objects, an array of resource identifier objects, or an empty array for requests that target resource collections
```
Feel free to import using `import "gitlab.com/kylehqcom/whatjson"` but you should probably just copy paste the code to remove dependencies.
The code works by checking for the next non whitespace character after a user supplied string prefix to confirm a JSON object or array.
## Example
Given this api response fragment
```
{
"data": [{
"id": "xyz",
"name": "Foo"
}]
}
```
Calling `WhatJSON()` with a prefix of `{"data":` will return an **Array** due to the next non whitespace character being a **[**
Calling `WhatJSON()` with a prefix of `{"data":[` will return an **Object** due to the next non whitespace character being a **{**
Calling `WhatJSON()` with an empty string prefix `""` will return an **Object** due to the first non whitespace character being a **{**, the outer object to data.
Calling `WhatJSON()` with a prefix of `{"da` will return an **Unknown** due to the first non whitespace character being the letter **t** in da**t**a.
Note that whitespace is trimmed/removed from your prefix so `{ "d a t a" : [` is treated identically to `{"data":[`
package whatjson
import (
"bufio"
"bytes"
"strings"
)
type JSONType string
const (
JSONArray JSONType = "array"
JSONObject JSONType = "object"
JSONUnknown JSONType = "unknown"
)
// WhatJSON will check for the next non whitespace char after prefix match to confirm a JSON object or array.
func WhatJSON(b []byte, prefix string) (JSONType, error) {
r := bufio.NewReader(bytes.NewReader(b))
defer r.UnreadByte()
prefixMatch := false
matchBuf := bytes.NewBufferString("")
// Strip all whitespace from the prefix string.
prefix = strings.Join(strings.Fields(prefix), "")
prefixBuf := bytes.NewBufferString(prefix)
if prefix == "" {
prefixMatch = true
}
for {
c, err := r.ReadByte()
if err != nil {
return JSONUnknown, err
}
// If we do not have a matched prefix, check the byte value and add to the buffer.
if !prefixMatch {
if c != ' ' && c != '\t' && c != '\r' && c != '\n' {
matchBuf.WriteByte(c)
if matchBuf.String() == prefixBuf.String() {
prefixMatch = true
}
}
continue
}
// We have a matched prefix so check the next byte for json type.
switch c {
case '[':
return JSONArray, nil
case '{':
return JSONObject, nil
case ' ', '\t', '\r', '\n':
// Deliberate fallthrough for whitespace
default:
return JSONUnknown, nil
}
}
}
package whatjson
import (
"testing"
"io"
)
func TestCheckJsonResponseType(t *testing.T) {
// Assert empty body
body := []byte("")
rt, err := WhatJSON(body, "")
if err != io.EOF {
t.Error("Expected EOF error")
}
if JSONUnknown != rt {
t.Error("Expected an unkown object on empty body.")
}
// Assert bad body prefix.
body = []byte("nope")
rt, err = WhatJSON(body, "")
if err != nil {
t.Error(err)
}
if JSONUnknown != rt {
t.Error("Expected an unkown object on bad body.")
}
// Assert empty prefix.
body = []byte(" { data:{[[{")
rt, err = WhatJSON(body, "")
if err != nil {
t.Error(err)
}
if JSONObject != rt {
t.Error("Expected an object as first char of body.")
}
// Assert non json prefix.
rt, err = WhatJSON(body, "bad")
if err == nil {
t.Error("Error should be set.")
}
if JSONUnknown != rt {
t.Error("Expected an unknown object for bad prefix.")
}
// Assert prefix with non json value next.
rt, err = WhatJSON(body, "{")
if err != nil {
t.Error(err)
}
if JSONUnknown != rt {
t.Error("Expected an unkown object.")
}
// Check prefix expecting object
rt, err = WhatJSON(body, "{data:")
if err != nil {
t.Error(err)
}
if JSONObject != rt {
t.Error("Expected an object.")
}
// Check prefix with spaces expecting object
rt, err = WhatJSON(body, " { data : ")
if err != nil {
t.Error(err)
}
if JSONObject != rt {
t.Error("Expected an object.")
}
// Check prefix expecting array
rt, err = WhatJSON(body, "{data:{")
if err != nil {
t.Error(err)
}
if JSONArray != rt {
t.Error("Expected an array.")
}
// Check prefix with spaces expecting array
rt, err = WhatJSON(body, " { d at a: {")
if err != nil {
t.Error(err)
}
if JSONArray != rt {
t.Error("Expected an array.")
}
// Check nesting.
rt, err = WhatJSON(body, "{data:{[")
if err != nil {
t.Error(err)
}
if JSONArray != rt {
t.Error("Expected an array.")
}
rt, err = WhatJSON(body, "{data:{[[")
if err != nil {
t.Error(err)
}
if JSONObject != rt {
t.Error("Expected an array.")
}
rt, err = WhatJSON(body, "{data:{[[{")
if err != io.EOF {
t.Error(err)
}
if JSONUnknown != rt {
t.Error("Expected an unknown.")
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment