cjson

package module
v0.3.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 2 Imported by: 0

README

cJSON

GoDoc GitHub release LLGo XGo

LLGo bindings for cJSON, enabling XGo/Go programs to parse, build and serialize JSON documents via the cJSON C API.

Installation

Install LLGo first, then install cJSON, its optional libcjson_utils library, and pkg-config for your platform. The binding links to both libraries through pkg-config.

These bindings are generated from cJSON 1.7.19, and the tests expect that exact library version. Package manager versions vary; if yours differs, use the source installation below to match the bindings.

macOS (Homebrew)
brew install cjson pkg-config
Linux

Ubuntu / Debian (libcjson-dev):

sudo apt update
sudo apt install libcjson-dev pkg-config

Fedora (cjson-devel):

sudo dnf install cjson-devel pkgconf-pkg-config

Arch Linux (cjson):

sudo pacman -S cjson pkgconf

Verify that your package provides both libcjson.pc and libcjson_utils.pc. Some older distribution packages build only the core library; use the source installation below with -DENABLE_CJSON_UTILS=ON in that case.

Windows

Set up the matching LLGo toolchain and pkg-config using the LLGo Windows guide, then install cJSON for the same architecture and ABI.

For MinGW, run the following in an MSYS2 CLANG64 shell (amd64):

pacman -S mingw-w64-clang-x86_64-cjson mingw-w64-clang-x86_64-pkgconf

For arm64, use a CLANGARM64 shell and replace mingw-w64-clang-x86_64- with mingw-w64-clang-aarch64-. See the MSYS2 cJSON packages. Keep the matching clang64/bin or clangarm64/bin directory on PATH when building and running programs so the cJSON DLL can be found.

For MSVC, install cJSON with your configured vcpkg (run from its directory in PowerShell):

.\vcpkg.exe install cjson:x64-windows pkgconf:x64-windows

Use arm64-windows instead of x64-windows for arm64. Configure pkg-config to use that triplet's metadata and add its bin directory to PATH, as described in the LLGo Windows guide. Keep MSVC and MinGW libraries in their respective toolchain environments.

If using WSL, follow the Linux instructions inside your WSL distribution and run Go and LLGo there.

From source (Linux / macOS, cJSON 1.7.19)

With Git, CMake, a C compiler, a build tool such as Make, and pkg-config installed, build the upstream v1.7.19 release:

git clone --depth 1 --branch v1.7.19 https://github.com/DaveGamble/cJSON.git cJSON-1.7.19
cmake -S cJSON-1.7.19 -B cJSON-1.7.19/build \
  -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_INSTALL_PREFIX="$HOME/.local" \
  -DCMAKE_INSTALL_LIBDIR=lib \
  -DBUILD_SHARED_LIBS=ON \
  -DENABLE_CJSON_UTILS=ON \
  -DENABLE_CJSON_TEST=OFF
cmake --build cJSON-1.7.19/build
cmake --install cJSON-1.7.19/build
export PKG_CONFIG_PATH="$HOME/.local/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}"

On Linux, also make the shared library available at runtime:

export LD_LIBRARY_PATH="$HOME/.local/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

Keep these environment variables set in the terminal where you build and run your program, or add them to your shell profile.

Install the Go binding

Verify that pkg-config finds the intended cJSON installation:

pkg-config --modversion libcjson libcjson_utils
pkg-config --libs libcjson_utils

Then, from your Go module directory:

go get github.com/llarhub/cjson

The binding also exposes the JSON Pointer, JSON Patch, and JSON Merge Patch helpers from cJSON_Utils.h; these methods require libcjson_utils at link and runtime.

Usage

Parse JSON and read a string field:

package main

import (
	"github.com/goplus/lib/c"
	"github.com/llarhub/cjson"
)

func main() {
	root := cjson.Parse(c.Str(`{"name":"LLGo"}`))
	if root == nil {
		panic("invalid JSON")
	}
	defer root.Delete()

	name := root.ObjectItemCaseSensitive(c.Str("name"))
	if name != nil && name.IsString() != 0 {
		println(c.GoString(name.StringValue()))
	}
}

Save the example as main.go and run it with llgo run ..

Use the cJSON Utils API to resolve a JSON Pointer:

package main

import (
	"github.com/goplus/lib/c"
	"github.com/llarhub/cjson"
)

func main() {
	root := cjson.Parse(c.Str(`{"user":{"name":"LLGo"}}`))
	if root == nil {
		panic("invalid JSON")
	}
	defer root.Delete()

	name := root.Pointer(c.Str("/user/name"))
	if name == nil || name.IsString() == 0 {
		panic("JSON Pointer did not resolve to a string")
	}
	println(c.GoString(name.StringValue()))
}

This prints LLGo. The same package also provides JSON Patch and JSON Merge Patch helpers such as GeneratePatches, ApplyPatches, and MergePatch.

Documentation

Index

Constants

View Source
const (
	VERSION_MAJOR = 1
	VERSION_MINOR = 7
	VERSION_PATCH = 19
)

project version

View Source
const (
	Invalid = 0
	False   = 1
	True    = 2
	NULL    = 4
	Number  = 8
	String  = 16
	Array   = 32
	Object  = 64
	// raw json
	Raw = 128
)

cJSON Types:

View Source
const (
	IsReference   = 256
	StringIsConst = 512
)
View Source
const CIRCULAR_LIMIT = 10000
View Source
const LLGoPackage = "link: $(pkg-config --libs libcjson_utils); -lcjson_utils -lcjson"
View Source
const NESTING_LIMIT = 1000

Variables

This section is empty.

Functions

func FindPointerFromObjectTo added in v0.3.3

func FindPointerFromObjectTo(object *Node, target *Node) *c.Char

Given a root object and a target object, construct a pointer from one to the other.

func Free

func Free(object unsafe.Pointer)

func GetErrorPtr

func GetErrorPtr() *c.Char

For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds.

func Malloc

func Malloc(size c.SizeT) unsafe.Pointer

malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks

func Minify

func Minify(json *c.Char)

Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. The input pointer json cannot point to a read-only address area, such as a string constant, but should point to a readable and writable address area.

func Version

func Version() *c.Char

returns the version of cJSON as a string

Types

type Bool

type Bool = c.Int

func AddItemReferenceToArray added in v0.3.3

func AddItemReferenceToArray(array *Node, item *Node) Bool

Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON.

func AddItemToArray added in v0.3.3

func AddItemToArray(array *Node, item *Node) Bool

Append item to the specified array/object.

type Hooks

type Hooks struct {
	MallocFn func(_llcppg_param1 c.SizeT) unsafe.Pointer
	FreeFn   func(_llcppg_param1 unsafe.Pointer)
}

llgo:type C

func (*Hooks) Init

func (self *Hooks) Init()

Supply malloc, realloc and free functions to cJSON

llgo:link (*Hooks).Init C.cJSON_InitHooks

type Node added in v0.3.0

type Node struct {
	Next        *Node
	Prev        *Node
	Child       *Node
	Type        c.Int
	Valuestring *c.Char
	Valueint    c.Int
	Valuedouble c.Double
	String      *c.Char
}

The cJSON structure:

func CreateArray

func CreateArray() *Node

func CreateBool

func CreateBool(boolean Bool) *Node

func CreateDoubleArray

func CreateDoubleArray(numbers *c.Double, count c.Int) *Node

func CreateFalse

func CreateFalse() *Node

func CreateFloatArray

func CreateFloatArray(numbers *c.Float, count c.Int) *Node

func CreateIntArray

func CreateIntArray(numbers *c.Int, count c.Int) *Node

These utilities create an Array of count items. The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.

func CreateNull

func CreateNull() *Node

These calls create a cJSON item of the appropriate type.

func CreateNumber

func CreateNumber(num c.Double) *Node

func CreateObject

func CreateObject() *Node

func CreateRaw

func CreateRaw(raw *c.Char) *Node

raw json

func CreateString

func CreateString(string *c.Char) *Node

raw json

func CreateStringArray

func CreateStringArray(strings **c.Char, count c.Int) *Node

func CreateStringReference

func CreateStringReference(string *c.Char) *Node

Create a string where valuestring references a string so it will not be freed by cJSON_Delete

func CreateTrue

func CreateTrue() *Node

func DetachItemViaPointer added in v0.3.3

func DetachItemViaPointer(parent *Node, item *Node) *Node

Remove/Detach items from Arrays/Objects.

func GenerateMergePatch added in v0.3.3

func GenerateMergePatch(from *Node, to *Node) *Node

NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key

func GenerateMergePatchCaseSensitive added in v0.3.3

func GenerateMergePatchCaseSensitive(from *Node, to *Node) *Node

func GeneratePatches added in v0.3.3

func GeneratePatches(from *Node, to *Node) *Node

NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key

func GeneratePatchesCaseSensitive added in v0.3.3

func GeneratePatchesCaseSensitive(from *Node, to *Node) *Node

Utility for generating patch array entries.

func Parse

func Parse(value *c.Char) *Node

Supply a block of JSON, and this returns a cJSON object you can interrogate.

func ParseWithLength

func ParseWithLength(value *c.Char, buffer_length c.SizeT) *Node

ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed.

func ParseWithLengthOpts

func ParseWithLengthOpts(value *c.Char, buffer_length c.SizeT, return_parse_end **c.Char, require_null_terminated Bool) *Node

func ParseWithOpts

func ParseWithOpts(value *c.Char, return_parse_end **c.Char, require_null_terminated Bool) *Node

If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr().

func (*Node) AddArrayToObject added in v0.3.0

func (self *Node) AddArrayToObject(name *c.Char) *Node

llgo:link (*Node).AddArrayToObject C.cJSON_AddArrayToObject

func (*Node) AddBoolToObject added in v0.3.0

func (self *Node) AddBoolToObject(name *c.Char, boolean Bool) *Node

llgo:link (*Node).AddBoolToObject C.cJSON_AddBoolToObject

func (*Node) AddFalseToObject added in v0.3.0

func (self *Node) AddFalseToObject(name *c.Char) *Node

llgo:link (*Node).AddFalseToObject C.cJSON_AddFalseToObject

func (*Node) AddItemReferenceToObject added in v0.3.0

func (self *Node) AddItemReferenceToObject(string *c.Char, item *Node) Bool

llgo:link (*Node).AddItemReferenceToObject C.cJSON_AddItemReferenceToObject

func (*Node) AddItemToObject added in v0.3.0

func (self *Node) AddItemToObject(string *c.Char, item *Node) Bool

Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before writing to `item->string`

llgo:link (*Node).AddItemToObject C.cJSON_AddItemToObject

func (*Node) AddItemToObjectCS added in v0.3.0

func (self *Node) AddItemToObjectCS(string *c.Char, item *Node) Bool

Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before writing to `item->string`

llgo:link (*Node).AddItemToObjectCS C.cJSON_AddItemToObjectCS

func (*Node) AddNullToObject added in v0.3.0

func (self *Node) AddNullToObject(name *c.Char) *Node

Helper functions for creating and adding items to an object at the same time. They return the added item or NULL on failure.

llgo:link (*Node).AddNullToObject C.cJSON_AddNullToObject

func (*Node) AddNumberToObject added in v0.3.0

func (self *Node) AddNumberToObject(name *c.Char, number c.Double) *Node

llgo:link (*Node).AddNumberToObject C.cJSON_AddNumberToObject

func (*Node) AddObjectToObject added in v0.3.0

func (self *Node) AddObjectToObject(name *c.Char) *Node

llgo:link (*Node).AddObjectToObject C.cJSON_AddObjectToObject

func (*Node) AddPatchToArray added in v0.3.0

func (self *Node) AddPatchToArray(operation *c.Char, path *c.Char, value *Node)

Utility for generating patch array entries.

llgo:link (*Node).AddPatchToArray C.cJSONUtils_AddPatchToArray

func (*Node) AddRawToObject added in v0.3.0

func (self *Node) AddRawToObject(name *c.Char, raw *c.Char) *Node

llgo:link (*Node).AddRawToObject C.cJSON_AddRawToObject

func (*Node) AddStringToObject added in v0.3.0

func (self *Node) AddStringToObject(name *c.Char, string *c.Char) *Node

llgo:link (*Node).AddStringToObject C.cJSON_AddStringToObject

func (*Node) AddTrueToObject added in v0.3.0

func (self *Node) AddTrueToObject(name *c.Char) *Node

llgo:link (*Node).AddTrueToObject C.cJSON_AddTrueToObject

func (*Node) ApplyPatches added in v0.3.0

func (self *Node) ApplyPatches(patches *Node) c.Int

Returns 0 for success.

llgo:link (*Node).ApplyPatches C.cJSONUtils_ApplyPatches

func (*Node) ApplyPatchesCaseSensitive added in v0.3.0

func (self *Node) ApplyPatchesCaseSensitive(patches *Node) c.Int

llgo:link (*Node).ApplyPatchesCaseSensitive C.cJSONUtils_ApplyPatchesCaseSensitive

func (*Node) ArrayItem added in v0.3.0

func (self *Node) ArrayItem(index c.Int) *Node

Retrieve item number "index" from array "array". Returns NULL if unsuccessful.

llgo:link (*Node).ArrayItem C.cJSON_GetArrayItem

func (*Node) ArraySize added in v0.3.0

func (self *Node) ArraySize() c.Int

Returns the number of items in an array (or object).

llgo:link (*Node).ArraySize C.cJSON_GetArraySize

func (*Node) Compare added in v0.3.0

func (self *Node) Compare(b *Node, case_sensitive Bool) Bool

Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0)

llgo:link (*Node).Compare C.cJSON_Compare

func (*Node) CreateArrayReference added in v0.3.0

func (self *Node) CreateArrayReference() *Node

llgo:link (*Node).CreateArrayReference C.cJSON_CreateArrayReference

func (*Node) CreateObjectReference added in v0.3.0

func (self *Node) CreateObjectReference() *Node

Create an object/array that only references it's elements so they will not be freed by cJSON_Delete

llgo:link (*Node).CreateObjectReference C.cJSON_CreateObjectReference

func (*Node) Delete added in v0.3.0

func (self *Node) Delete()

Delete a cJSON entity and all subentities.

llgo:link (*Node).Delete C.cJSON_Delete

func (*Node) DeleteItemFromArray added in v0.3.0

func (self *Node) DeleteItemFromArray(which c.Int)

llgo:link (*Node).DeleteItemFromArray C.cJSON_DeleteItemFromArray

func (*Node) DeleteItemFromObject added in v0.3.0

func (self *Node) DeleteItemFromObject(string *c.Char)

llgo:link (*Node).DeleteItemFromObject C.cJSON_DeleteItemFromObject

func (*Node) DeleteItemFromObjectCaseSensitive added in v0.3.0

func (self *Node) DeleteItemFromObjectCaseSensitive(string *c.Char)

llgo:link (*Node).DeleteItemFromObjectCaseSensitive C.cJSON_DeleteItemFromObjectCaseSensitive

func (*Node) DetachItemFromArray added in v0.3.0

func (self *Node) DetachItemFromArray(which c.Int) *Node

llgo:link (*Node).DetachItemFromArray C.cJSON_DetachItemFromArray

func (*Node) DetachItemFromObject added in v0.3.0

func (self *Node) DetachItemFromObject(string *c.Char) *Node

llgo:link (*Node).DetachItemFromObject C.cJSON_DetachItemFromObject

func (*Node) DetachItemFromObjectCaseSensitive added in v0.3.0

func (self *Node) DetachItemFromObjectCaseSensitive(string *c.Char) *Node

llgo:link (*Node).DetachItemFromObjectCaseSensitive C.cJSON_DetachItemFromObjectCaseSensitive

func (*Node) Duplicate added in v0.3.0

func (self *Node) Duplicate(recurse Bool) *Node

Duplicate a cJSON item

llgo:link (*Node).Duplicate C.cJSON_Duplicate

func (*Node) HasObjectItem added in v0.3.0

func (self *Node) HasObjectItem(string *c.Char) Bool

For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds.

llgo:link (*Node).HasObjectItem C.cJSON_HasObjectItem

func (*Node) InsertItemInArray added in v0.3.0

func (self *Node) InsertItemInArray(which c.Int, newitem *Node) Bool

Update array items.

llgo:link (*Node).InsertItemInArray C.cJSON_InsertItemInArray

func (*Node) IsArray added in v0.3.0

func (self *Node) IsArray() Bool

llgo:link (*Node).IsArray C.cJSON_IsArray

func (*Node) IsBool added in v0.3.0

func (self *Node) IsBool() Bool

llgo:link (*Node).IsBool C.cJSON_IsBool

func (*Node) IsFalse added in v0.3.0

func (self *Node) IsFalse() Bool

llgo:link (*Node).IsFalse C.cJSON_IsFalse

func (*Node) IsInvalid added in v0.3.0

func (self *Node) IsInvalid() Bool

These functions check the type of an item

llgo:link (*Node).IsInvalid C.cJSON_IsInvalid

func (*Node) IsNull added in v0.3.0

func (self *Node) IsNull() Bool

llgo:link (*Node).IsNull C.cJSON_IsNull

func (*Node) IsNumber added in v0.3.0

func (self *Node) IsNumber() Bool

llgo:link (*Node).IsNumber C.cJSON_IsNumber

func (*Node) IsObject added in v0.3.0

func (self *Node) IsObject() Bool

llgo:link (*Node).IsObject C.cJSON_IsObject

func (*Node) IsRaw added in v0.3.0

func (self *Node) IsRaw() Bool

llgo:link (*Node).IsRaw C.cJSON_IsRaw

func (*Node) IsString added in v0.3.0

func (self *Node) IsString() Bool

llgo:link (*Node).IsString C.cJSON_IsString

func (*Node) IsTrue added in v0.3.0

func (self *Node) IsTrue() Bool

llgo:link (*Node).IsTrue C.cJSON_IsTrue

func (*Node) MergePatch added in v0.3.0

func (self *Node) MergePatch(patch *Node) *Node

target will be modified by patch. return value is new ptr for target.

llgo:link (*Node).MergePatch C.cJSONUtils_MergePatch

func (*Node) MergePatchCaseSensitive added in v0.3.0

func (self *Node) MergePatchCaseSensitive(patch *Node) *Node

generates a patch to move from -> to

llgo:link (*Node).MergePatchCaseSensitive C.cJSONUtils_MergePatchCaseSensitive

func (*Node) NumberValue added in v0.3.0

func (self *Node) NumberValue() c.Double

llgo:link (*Node).NumberValue C.cJSON_GetNumberValue

func (*Node) ObjectItem added in v0.3.0

func (self *Node) ObjectItem(string *c.Char) *Node

Get item "string" from object. Case insensitive.

llgo:link (*Node).ObjectItem C.cJSON_GetObjectItem

func (*Node) ObjectItemCaseSensitive added in v0.3.0

func (self *Node) ObjectItemCaseSensitive(string *c.Char) *Node

llgo:link (*Node).ObjectItemCaseSensitive C.cJSON_GetObjectItemCaseSensitive

func (*Node) Pointer added in v0.3.0

func (self *Node) Pointer(pointer *c.Char) *Node

Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec.

llgo:link (*Node).Pointer C.cJSONUtils_GetPointer

func (*Node) PointerCaseSensitive added in v0.3.0

func (self *Node) PointerCaseSensitive(pointer *c.Char) *Node

llgo:link (*Node).PointerCaseSensitive C.cJSONUtils_GetPointerCaseSensitive

func (*Node) Print added in v0.3.0

func (self *Node) Print() *c.Char

Render a cJSON entity to text for transfer/storage.

llgo:link (*Node).Print C.cJSON_Print

func (*Node) PrintBuffered added in v0.3.0

func (self *Node) PrintBuffered(prebuffer c.Int, fmt Bool) *c.Char

Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted

llgo:link (*Node).PrintBuffered C.cJSON_PrintBuffered

func (*Node) PrintPreallocated added in v0.3.0

func (self *Node) PrintPreallocated(buffer *c.Char, length c.Int, format Bool) Bool

NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need

llgo:link (*Node).PrintPreallocated C.cJSON_PrintPreallocated

func (*Node) PrintUnformatted added in v0.3.0

func (self *Node) PrintUnformatted() *c.Char

Render a cJSON entity to text for transfer/storage without any formatting.

llgo:link (*Node).PrintUnformatted C.cJSON_PrintUnformatted

func (*Node) ReplaceItemInArray added in v0.3.0

func (self *Node) ReplaceItemInArray(which c.Int, newitem *Node) Bool

llgo:link (*Node).ReplaceItemInArray C.cJSON_ReplaceItemInArray

func (*Node) ReplaceItemInObject added in v0.3.0

func (self *Node) ReplaceItemInObject(string *c.Char, newitem *Node) Bool

llgo:link (*Node).ReplaceItemInObject C.cJSON_ReplaceItemInObject

func (*Node) ReplaceItemInObjectCaseSensitive added in v0.3.0

func (self *Node) ReplaceItemInObjectCaseSensitive(string *c.Char, newitem *Node) Bool

llgo:link (*Node).ReplaceItemInObjectCaseSensitive C.cJSON_ReplaceItemInObjectCaseSensitive

func (*Node) ReplaceItemViaPointer added in v0.3.0

func (self *Node) ReplaceItemViaPointer(item *Node, replacement *Node) Bool

llgo:link (*Node).ReplaceItemViaPointer C.cJSON_ReplaceItemViaPointer

func (*Node) SetNumberHelper added in v0.3.0

func (self *Node) SetNumberHelper(number c.Double) c.Double

helper for the cJSON_SetNumberValue macro

llgo:link (*Node).SetNumberHelper C.cJSON_SetNumberHelper

func (*Node) SetValuestring added in v0.3.0

func (self *Node) SetValuestring(valuestring *c.Char) *c.Char

Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String

llgo:link (*Node).SetValuestring C.cJSON_SetValuestring

func (*Node) SortObject added in v0.3.0

func (self *Node) SortObject()

Sorts the members of the object into alphabetical order.

llgo:link (*Node).SortObject C.cJSONUtils_SortObject

func (*Node) SortObjectCaseSensitive added in v0.3.0

func (self *Node) SortObjectCaseSensitive()

llgo:link (*Node).SortObjectCaseSensitive C.cJSONUtils_SortObjectCaseSensitive

func (*Node) StringValue added in v0.3.0

func (self *Node) StringValue() *c.Char

Check item type and return its value

llgo:link (*Node).StringValue C.cJSON_GetStringValue

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL