Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: CI

on:
push:
branches: [master]
pull_request:
branches: [master]

jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.16", "1.17", "1.18", "1.19", "1.20", "1.21"]

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}

- name: Get dependencies
run: go get -v -t -d ./...

- name: Build
run: go build -v ./...

- name: Run tests
run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...

- name: Upload coverage to Codecov
if: matrix.go-version == '1.21'
uses: codecov/codecov-action@v3
with:
file: ./coverage.txt
flags: unittests
name: codecov-umbrella

lint:
name: Lint
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21"

- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: latest
args: --timeout=5m

format:
name: Format Check
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21"

- name: Check formatting
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "The following files are not formatted:"
gofmt -s -l .
echo "Please run 'gofmt -s -w .' to format your code."
exit 1
fi

vet:
name: Go Vet
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21"

- name: Run go vet
run: go vet ./...

staticcheck:
name: Static Check
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: "1.21"

- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@latest

- name: Run staticcheck
run: staticcheck ./...
53 changes: 53 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
run:
timeout: 5m
tests: true

linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
- gocritic
- gosec
- revive

linters-settings:
errcheck:
check-blank: true
check-type-assertions: true

govet:
check-shadowing: true

revive:
severity: warning
rules:
- name: blank-imports
- name: context-as-argument
- name: dot-imports
- name: error-return
- name: error-strings
- name: error-naming
- name: exported
- name: increment-decrement
- name: var-naming
- name: package-comments
- name: range
- name: receiver-naming
- name: indent-error-flow
- name: superfluous-else
- name: unreachable-code

issues:
exclude-use-default: false
max-issues-per-linter: 0
max-same-issues: 0
60 changes: 60 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.PHONY: test
test:
go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...

.PHONY: bench
bench:
go test -bench=. -benchmem ./...

.PHONY: fmt
fmt:
gofmt -s -w .

.PHONY: fmt-check
fmt-check:
@if [ "$$(gofmt -s -l . | wc -l)" -gt 0 ]; then \
echo "The following files are not formatted:"; \
gofmt -s -l .; \
echo "Please run 'make fmt' to format your code."; \
exit 1; \
fi

.PHONY: vet
vet:
go vet ./...

.PHONY: lint
lint:
golangci-lint run

.PHONY: check
check: fmt-check vet test
@echo "All checks passed!"

.PHONY: build
build:
go build -v ./...

.PHONY: clean
clean:
go clean
rm -f coverage.txt

.PHONY: coverage
coverage: test
go tool cover -html=coverage.txt

.PHONY: help
help:
@echo "Available targets:"
@echo " test - Run tests with race detector and coverage"
@echo " bench - Run benchmarks"
@echo " fmt - Format code with gofmt"
@echo " fmt-check - Check if code is formatted"
@echo " vet - Run go vet"
@echo " lint - Run golangci-lint"
@echo " check - Run fmt-check, vet, and test"
@echo " build - Build the package"
@echo " clean - Clean build artifacts"
@echo " coverage - Generate and open coverage report"
@echo " help - Show this help message"
111 changes: 106 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@ commas, such as:
}
```

There's a provided function `jsonc.ToJSON`, which does the conversion.
There are several functions available for working with JSONC:

The resulting JSON will always be the same length as the input and it will
- `jsonc.ToJSON` - Converts JSONC to standard JSON (strips comments and trailing commas)
- `jsonc.Parse` - Parses JSONC into a document structure that preserves formatting
- Document methods for reading and modifying JSONC while preserving formatting:
- `Get(path)` - Get a value at a given path
- `Set(path, value)` - Set a value at a given path
- `Delete(path)` - Delete a value at a given path
- `ToJSONC()` - Convert back to JSONC format with preserved formatting

The `ToJSON` function ensures the resulting JSON will always be the same length as the input and it will
include all of the same line breaks at matching offsets. This is to ensure
the result can be later processed by a external parser and that that
parser will report messages or errors with the correct offsets.
Expand All @@ -47,14 +55,15 @@ $ go get -u github.com/tidwall/jsonc

This will retrieve the library.

### Example
### Examples

#### Converting JSONC to JSON

The following example uses a JSON document that has comments and trailing
The following example uses a JSONC document that has comments and trailing
commas and converts it just prior to unmarshalling with the standard Go
JSON library.

```go

data := `
{
/* Dev Machine */
Expand All @@ -74,7 +83,99 @@ data := `
`

err := json.Unmarshal(jsonc.ToJSON(data), &config)
```

#### Parsing and Modifying JSONC

The following example shows how to parse JSONC, modify values, and write back to JSONC format while preserving comments and formatting:

```go
import "github.com/tidwall/jsonc"

// Original JSONC with comments and trailing commas
data := `{
// Database configuration
"database": {
"host": "localhost",
"port": 5432,
"user": "dev",
},
"features": [
"auth",
"logging", // Important for debugging
]
}`

// Parse JSONC
doc, err := jsonc.Parse([]byte(data))
if err != nil {
panic(err)
}

// Read values
host, err := doc.Get("database.host")
if err != nil {
panic(err)
}
fmt.Printf("Database host: %s\n", host)

// Modify existing values
err = doc.Set("database.port", 3306)
if err != nil {
panic(err)
}

// Add new values
err = doc.Set("database.password", "secret123")
if err != nil {
panic(err)
}

// Add nested structures
err = doc.Set("cache.redis.host", "redis.example.com")
if err != nil {
panic(err)
}

// Delete values
err = doc.Delete("features.1") // Remove "logging" feature
if err != nil {
panic(err)
}

// Convert back to JSONC format (preserves comments and trailing commas)
result, err := doc.ToJSONC()
if err != nil {
panic(err)
}

fmt.Printf("Modified JSONC:\n%s\n", result)
```

The modified JSONC output will preserve comments, trailing commas, and formatting while incorporating your changes.

#### Path Syntax

Paths use dot notation to access nested values:

- `"key"` - Access a property in an object
- `"parent.child"` - Access nested properties
- `"array.0"` - Access array elements by index
- `"parent.array.1.property"` - Complex nested access

Examples:
```go
// Object access
value, _ := doc.Get("database.host")

// Array access
firstFeature, _ := doc.Get("features.0")

// Nested array/object access
nestedValue, _ := doc.Get("users.0.profile.name")

// Setting creates intermediate objects/arrays as needed
doc.Set("new.nested.value", "hello")
```

### Performance
Expand Down
Loading