diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..9c71a03 --- /dev/null +++ b/errors.go @@ -0,0 +1,14 @@ +package jsonstr + +import ( + "sourcecode.social/reiver/go-erorr" +) + +const ( + ErrNotJSONArrayOfString = erorr.Error("jsonstr: not JSON array of strings") +) + +const ( + errNilReceiver = erorr.Error("jsonstr: nil receiver") + errNilData = erorr.Error("jsonstr: nil data") +) diff --git a/strings.go b/strings.go new file mode 100644 index 0000000..6524088 --- /dev/null +++ b/strings.go @@ -0,0 +1,100 @@ +package jsonstr + +import ( + "bytes" + "encoding/json" +) + +var _ json.Marshaler = Strings{} +var _ json.Unmarshaler = new(Strings) + +// Strings represents a JSON array of strings. +// +// For example: +// +// var strings jsonstr.Strings +// +// // ... +// +// jason := []byte(`["once", "twice", "thrice", "fource"]`) +// +// err := json.Unmarshal(jason, &strings) +type Strings struct { + value string +} + +func Compile(a ...string) Strings { + if len(a) <= 0 { + return Strings{ + value:"[]", + } + } + + // This should not return an error, since this is a []string. + p, _ := json.Marshal(a) + + return Strings{ + value: string(p), + } +} + +func (receiver Strings) Decompile() []string { + if "" == receiver.value { + return []string{} + } + + var ss []string + json.Unmarshal([]byte(receiver.value), &ss) + if nil == ss { + ss = []string{} + } + + return ss +} + +var emptyJSONArray []byte = []byte{'[',']'} + +func (receiver Strings) MarshalJSON() ([]byte, error) { + if "" == receiver.value { + return emptyJSONArray, nil + } + + return []byte(receiver.value), nil +} + +func (receiver *Strings) UnmarshalJSON(data []byte) error { + if nil == receiver { + return errNilReceiver + } + if nil == data { + return errNilData + } + + var buffer bytes.Buffer + { + + if err := json.Compact(&buffer, data); nil != err { + return err + } + } + + s := buffer.String() + + if "[]" == s { + receiver.value = "" + return nil + } + + { + validated, err := validate(buffer.Bytes()) + if nil != err { + return err + } + if !validated { + return ErrNotJSONArrayOfString + } + } + + receiver.value = s + return nil +} diff --git a/strings_marshaljson_test.go b/strings_marshaljson_test.go new file mode 100644 index 0000000..5c6bb72 --- /dev/null +++ b/strings_marshaljson_test.go @@ -0,0 +1,81 @@ +package jsonstr_test + +import ( + "testing" + + "encoding/json" + + "sourcecode.social/reiver/go-jsonstr" +) + +func TestString_MarshalJSON(t *testing.T) { + + tests := []struct{ + Strings jsonstr.Strings + Expected string + }{ + { + Strings: jsonstr.Strings{}, + Expected: `[]`, + }, + + + + { + Strings: jsonstr.Compile("apple"), + Expected: `["apple"]`, + }, + { + Strings: jsonstr.Compile("apple", "banana"), + Expected: `["apple","banana"]`, + }, + { + Strings: jsonstr.Compile("apple", "banana", "cherry"), + Expected: `["apple","banana","cherry"]`, + }, + + + + { + Strings: jsonstr.Compile("😈"), + Expected: `["😈"]`, + }, + { + Strings: jsonstr.Compile("😈", "🙂🙁"), + Expected: `["😈","🙂🙁"]`, + }, + { + Strings: jsonstr.Compile("😈", "🙂🙁", ""), + Expected: `["😈","🙂🙁",""]`, + }, + { + Strings: jsonstr.Compile("😈", "🙂🙁", "", "٠١٢٣۴۵۶٧٨٩"), + Expected: `["😈","🙂🙁","","٠١٢٣۴۵۶٧٨٩"]`, + }, + } + + for testNumber, test := range tests { + + actualBytes, err := json.Marshal(test.Strings) + if nil != err { + t.Errorf("For test #%d, did not expect an error but actually got one.", testNumber) + t.Logf("ERROR: (%T) %s", err, err) + t.Logf("STRINGS: %#v", test.Strings) + t.Logf("EXPECTED: %#v", test.Expected) + continue + } + + { + actual := string(actualBytes) + expected := test.Expected + + if expected != actual { + t.Errorf("For test #%d, the actual marshaled value is not what was expected.", testNumber) + t.Logf("EXPECTED: %s", expected) + t.Logf("ACTUAL: %s", actual) + t.Logf("STRINGS: %#v", test.Strings) + continue + } + } + } +} diff --git a/strings_unmarshaljson_test.go b/strings_unmarshaljson_test.go new file mode 100644 index 0000000..07eb731 --- /dev/null +++ b/strings_unmarshaljson_test.go @@ -0,0 +1,194 @@ +package jsonstr_test + +import ( + "testing" + + "encoding/json" + + "sourcecode.social/reiver/go-jsonstr" +) + +func TestString_UnmarshalJSON(t *testing.T) { + + tests := []struct{ + JSON string + Expected jsonstr.Strings + }{ + { + JSON: `[]`, + Expected: jsonstr.Strings{}, + }, + { + JSON: `[ ]`, + Expected: jsonstr.Strings{}, + }, + { + JSON: `[ ]`, + Expected: jsonstr.Strings{}, + }, + + + + { + JSON: `["apple"]`, + Expected: jsonstr.Compile("apple"), + }, + { + JSON: `["apple","banana"]`, + Expected: jsonstr.Compile("apple","banana"), + }, + { + JSON: `["apple","banana","cherry"]`, + Expected: jsonstr.Compile("apple","banana","cherry"), + }, + + + + { + JSON: `["😈"]`, + Expected: jsonstr.Compile("😈"), + }, + { + JSON: `["😈","🙂🙁"]`, + Expected: jsonstr.Compile("😈","🙂🙁"), + }, + { + JSON: `["😈","🙂🙁",""]`, + Expected: jsonstr.Compile("😈","🙂🙁",""), + }, + { + JSON: `["😈","🙂🙁","","٠١٢٣۴۵۶٧٨٩"]`, + Expected: jsonstr.Compile("😈","🙂🙁","","٠١٢٣۴۵۶٧٨٩"), + }, + + + + { + JSON: `["1","two", "THREE", "iv", "۵"]`, + Expected: jsonstr.Compile("1","two", "THREE", "iv", "۵"), + }, + } + + for testNumber, test := range tests { + + var actual jsonstr.Strings + + err := json.Unmarshal([]byte(test.JSON), &actual) + if nil != err { + t.Errorf("For test #%d, did not expect an error but actually got one.", testNumber) + t.Logf("ERROR: (%T) %s", err, err) + t.Logf("JSON: %#v", test.JSON) + t.Logf("EXPECTED: %#v", test.Expected) + continue + } + + { + expected := test.Expected + + if expected != actual { + t.Errorf("For test #%d, the actual marshaled value is not what was expected.", testNumber) + t.Logf("EXPECTED: %#v", expected) + t.Logf("ACTUAL: %#v", actual) + t.Logf("JSON: %#v", test.JSON) + continue + } + } + } +} + +func TestString_UnmarshalJSON_fail(t *testing.T) { + + tests := []struct{ + JSON string + }{ + { + JSON: `{}`, + }, + { + JSON: `{"name":"value"}`, + }, + + + + { + JSON: `false`, + }, + { + JSON: `true`, + }, + + + + { + JSON: `-2.223`, + }, + { + JSON: `-1`, + }, + { + JSON: `0`, + }, + { + JSON: `11`, + }, + { + JSON: `222.22`, + }, + { + JSON: `3333`, + }, + { + JSON: `44444`, + }, + { + JSON: `5.0`, + }, + + + + { + JSON: `[false]`, + }, + { + JSON: `[false, true]`, + }, + { + JSON: `[false, false, false, false, true, true, true, false]`, + }, + + + + { + JSON: `["hello", 1, "wow", true, -3.2, "world"]`, + }, + + { + JSON: `["once","twice","thrice","fource",5]`, + }, + } + + for testNumber, test := range tests { + + var actual jsonstr.Strings + + err := json.Unmarshal([]byte(test.JSON), &actual) + if nil == err { + t.Errorf("For test #%d, expected an error but did not actually get one.", testNumber) + t.Logf("JSON: %#v", test.JSON) + continue + } + + { + expected := jsonstr.ErrNotJSONArrayOfString + actual := err + + if expected != actual { + t.Errorf("For test #%d, the actual error is not what was expected.", testNumber) + t.Logf("EXPECTED ERROR: (%T) %s", expected, expected) + t.Logf("ACTUAL ERROR: (%T) %s", actual, actual) + t.Logf("JSON: %#v", test.JSON) + continue + } + } + } +} diff --git a/validate.go b/validate.go new file mode 100644 index 0000000..9add1a2 --- /dev/null +++ b/validate.go @@ -0,0 +1,25 @@ +package jsonstr + +import ( + "encoding/json" +) + +func validate(data []byte) (bool, error) { + if nil == data { + return false, errNilData + } + + var ss []string + + err := json.Unmarshal(data, &ss) + if nil != err { + switch err.(type) { + case *json.UnmarshalTypeError: + return false, nil + default: + return false, err + } + } + + return true, nil +}