├── LICENSE ├── README.md ├── go.mod ├── jsonc.go └── jsonc_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Josh Baker 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonc 2 | 3 | [![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/tidwall/jsonc) 4 | 5 | jsonc is a Go package that converts the jsonc format to standard json. 6 | 7 | The jsonc format is like standard json but allows for comments and trailing 8 | commas, such as: 9 | 10 | ```js 11 | { 12 | 13 | /* Dev Machine */ 14 | "dbInfo": { 15 | "host": "localhost", 16 | "port": 5432, 17 | "username": "josh", 18 | "password": "pass123", // please use a hashed password 19 | }, 20 | 21 | /* Only SMTP Allowed */ 22 | "emailInfo": { 23 | "email": "josh@example.com", // use full email address 24 | "password": "pass123", 25 | "smtp": "smpt.example.com", 26 | } 27 | 28 | } 29 | ``` 30 | 31 | There's a provided function `jsonc.ToJSON`, which does the conversion. 32 | 33 | The resulting JSON will always be the same length as the input and it will 34 | include all of the same line breaks at matching offsets. This is to ensure 35 | the result can be later processed by a external parser and that that 36 | parser will report messages or errors with the correct offsets. 37 | 38 | ## Getting Started 39 | 40 | ### Installing 41 | 42 | To start using jsonc, install Go and run `go get`: 43 | 44 | ```sh 45 | $ go get -u github.com/tidwall/jsonc 46 | ``` 47 | 48 | This will retrieve the library. 49 | 50 | ### Example 51 | 52 | The following example uses a JSON document that has comments and trailing 53 | commas and converts it just prior to unmarshalling with the standard Go 54 | JSON library. 55 | 56 | ```go 57 | 58 | data := ` 59 | { 60 | /* Dev Machine */ 61 | "dbInfo": { 62 | "host": "localhost", 63 | "port": 5432, // use full email address 64 | "username": "josh", 65 | "password": "pass123", // use a hashed password 66 | }, 67 | /* Only SMTP Allowed */ 68 | "emailInfo": { 69 | "email": "josh@example.com", 70 | "password": "pass123", 71 | "smtp": "smpt.example.com", 72 | } 73 | } 74 | ` 75 | 76 | err := json.Unmarshal(jsonc.ToJSON(data), &config) 77 | 78 | ``` 79 | 80 | ### Performance 81 | 82 | It's fast and can convert GB/s of jsonc to json. 83 | 84 | ## Contact 85 | 86 | Josh Baker [@tidwall](http://twitter.com/tidwall) 87 | 88 | ## License 89 | 90 | jsonc source code is available under the MIT [License](/LICENSE). 91 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tidwall/jsonc 2 | 3 | go 1.16 4 | -------------------------------------------------------------------------------- /jsonc.go: -------------------------------------------------------------------------------- 1 | package jsonc 2 | 3 | // ToJSON strips out comments and trailing commas and convert the input to a 4 | // valid JSON per the official spec: https://tools.ietf.org/html/rfc8259 5 | // 6 | // The resulting JSON will always be the same length as the input and it will 7 | // include all of the same line breaks at matching offsets. This is to ensure 8 | // the result can be later processed by a external parser and that that 9 | // parser will report messages or errors with the correct offsets. 10 | func ToJSON(src []byte) []byte { 11 | return toJSON(src, nil) 12 | } 13 | 14 | // ToJSONInPlace is the same as ToJSON, but this method reuses the input json 15 | // buffer to avoid allocations. Do not use the original bytes slice upon return. 16 | func ToJSONInPlace(src []byte) []byte { 17 | return toJSON(src, src) 18 | } 19 | 20 | func toJSON(src, dst []byte) []byte { 21 | dst = dst[:0] 22 | for i := 0; i < len(src); i++ { 23 | if src[i] == '/' { 24 | if i < len(src)-1 { 25 | if src[i+1] == '/' { 26 | dst = append(dst, ' ', ' ') 27 | i += 2 28 | for ; i < len(src); i++ { 29 | if src[i] == '\n' { 30 | dst = append(dst, '\n') 31 | break 32 | } else if src[i] == '\t' || src[i] == '\r' { 33 | dst = append(dst, src[i]) 34 | } else { 35 | dst = append(dst, ' ') 36 | } 37 | } 38 | continue 39 | } 40 | if src[i+1] == '*' { 41 | dst = append(dst, ' ', ' ') 42 | i += 2 43 | for ; i < len(src)-1; i++ { 44 | if src[i] == '*' && src[i+1] == '/' { 45 | dst = append(dst, ' ', ' ') 46 | i++ 47 | break 48 | } else if src[i] == '\n' || src[i] == '\t' || 49 | src[i] == '\r' { 50 | dst = append(dst, src[i]) 51 | } else { 52 | dst = append(dst, ' ') 53 | } 54 | } 55 | continue 56 | } 57 | } 58 | } 59 | dst = append(dst, src[i]) 60 | if src[i] == '"' { 61 | for i = i + 1; i < len(src); i++ { 62 | dst = append(dst, src[i]) 63 | if src[i] == '"' { 64 | j := i - 1 65 | for ; ; j-- { 66 | if src[j] != '\\' { 67 | break 68 | } 69 | } 70 | if (j-i)%2 != 0 { 71 | break 72 | } 73 | } 74 | } 75 | } else if src[i] == '}' || src[i] == ']' { 76 | for j := len(dst) - 2; j >= 0; j-- { 77 | if dst[j] <= ' ' { 78 | continue 79 | } 80 | if dst[j] == ',' { 81 | dst[j] = ' ' 82 | } 83 | break 84 | } 85 | } 86 | } 87 | return dst 88 | } 89 | -------------------------------------------------------------------------------- /jsonc_test.go: -------------------------------------------------------------------------------- 1 | package jsonc 2 | 3 | import "testing" 4 | 5 | func TestToJSON(t *testing.T) { 6 | json := ` 7 | { // hello 8 | "c": 3,"b":3, // jello 9 | /* SOME 10 | LIKE 11 | IT 12 | HAUT */ 13 | "d\\\"\"e": [ 1, /* 2 */ 3, 4, ], 14 | }` 15 | expect := ` 16 | { 17 | "c": 3,"b":3, 18 | 19 | 20 | 21 | 22 | "d\\\"\"e": [ 1, 3, 4 ] 23 | }` 24 | out := string(ToJSON([]byte(json))) 25 | if out != expect { 26 | t.Fatalf("expected '%s', got '%s'", expect, out) 27 | } 28 | out = string(ToJSONInPlace([]byte(json))) 29 | if out != expect { 30 | t.Fatalf("expected '%s', got '%s'", expect, out) 31 | } 32 | } 33 | --------------------------------------------------------------------------------