Base Class and Methods for 'gson' Format
Overview
Provides a lightweight container and exchange format for gene set collections. A 'GSON' object stores which genes belong to which gene set, together with gene set and gene names, the identifier types in use, species, versions and source metadata. A collection can be built from data frames, read from and written to the 'gson' JavaScript Object Notation (JSON) format and the 'GMT' format, subset by gene set, merged across sources, validated, and resolved to the web addresses of the databases it comes from, so that a collection gathered by one package can be analysed by another.
- keep the documented columns of
gsid2gene,gsid2nameandgene2nameas character ingson(), so that a producer passing a factor no longer leaks it into the object, and a double column is stored by its digits --as.character(1e6)prints"1e+06", soformat(scientific = FALSE)is used for doubles.clusterProfiler::gson_GO()buildsgsid2genewithutils::stack(), whoseindcolumn is a factor, and a factor retains the levels it no longer uses: everysplit(gsid2gene$gene, gsid2gene$gsid)then invents one empty gene set per unused level -- forgson_GO("org.Hs.eg.db", ont = "CC")that is 4069 gene sets of which 2067 are empty, although only 2002 terms carry annotations -- andlength(),names(),write.gmt()andwrite.gson()report those phantom sets as real ones (write.gmt()emits agsid<TAB>descriptionline with no gene,write.gson()a"gsid": []entry). The phantoms reach the consumers through their ownsplit():enrichit::ora_gson(),gsea_gson(),nsea_gson()andDOSE::getGeneSet().write.gson()/read.gson()also stopped round-tripping to an identical object, since JSON has no factor type andread.gson()coerced the columns back to character; that coercion now lives in one place,gson(), and the duplicated lines inread.gson()are gone (2026-10-08, Thu) - check the structural contract of the three mapping tables when a
GSONis created, so an object assembled withnew("GSON", ...)is refused byvalidObject()when a table is not adata.frame, lacks the documented columns, or holds identifiers that are not character. Only the structure is enforced here: duplicated identifiers, missing cross-references between the tables, empty tables and non-scalar metadata stay withvalidate_gson(), which reports them instead of refusing them, and objects that violate those rules must remain constructible so that they can be inspected (2026-10-08, Thu) write.gmt()andwrite.gson()now skip memberships that carry no gene set ID or no gene, and warn about how many were omitted. An emptygsidused to abortwrite.gmt()withsubscript out of bounds, and a missinggeneused to reach the output as the literalNA-- a GMT field ofNA, a[null]member in the JSON -- which the consumers read back as a gene genuinely calledNAand hand on to the enrichment routines.write.gmt()also falls back to the gene set ID whengsid2nameholds no usable name for a set, so the description column can no longer be the stringNAeither. Objects whose memberships are all usable are written unchanged and silently, andwrite.gson()/read.gson()still round-trip them to an identical object (2026-10-08, Thu)gson()also drops the row labels the producers leave on the three mapping tables, so that an object whose memberships are already grouped by gene set ID reads back fromwrite.gson()/read.gson()as the object it was.clusterProfiler::gson_GO("org.Hs.eg.db", ont = "CC")rows itsgsid2nameby GO term and inherits forgsid2genethe row indices of the annotation table it was subset from, while JSON carries no row names andread.gson()can only produce the default sequence:identical()of the two objects was FALSE for that 2002-set, 378845-membership object even though every value matched, and it is TRUE now. The labels repeat what the columns already hold, so dropping them gives up nothing. What the format still cannot carry is the row order ofgsid2gene, since a.gsonfile keys the memberships bygsidand a table that was not already grouped by gene set ID comes back regrouped -- the gene sets and the order of the genes within each are preserved (2026-10-08, Thu)GSONgainsgsidtype, the identifier domain the gene set IDs are drawn from (GO,KEGG,REAC, ...), so that a consumer can stop inferring it from the shape of the identifiers --GOSemSim::parse_gson()currently repairs a missingGO:prefix and rejects a collection withno GO ID foundby regular expression, which is exactly the guess this field replaces. It sits besidekeytype, which describes the genes rather than the gene set IDs, and is threaded throughgson(),read.gson(),write.gson(),as.list(),validate_gson()andprint(). It is free text on purpose:validate_gson()asks only for one value orNULL, because an enforced enum would reject every producer that names its domain differently. Files written before the field existed read it back asNULL, and a file that carries it still opens in the released 0.2.1, which ignores the unknown key (verified by reading agsidtypefile with the CRAN build).schema_versionis not bumped for it --gson()still writes1.0and a producer that wants to declare1.1says so -- butvalidate_gson()now reports aschema_versionit does not recognise instead of staying silent, and never rewrites the value (2026-10-08, Thu)urlpatterncan now be consumed:gson_url()returns the URLs of the gene sets, one per ID and defaulting to everynames(x)of the object, andbrowseGS()opens them in a browser. Both were reachable before this release only from inside the package --browseGS()was never exported, and itssub()call passed the pattern as the string to search, so a vector of gene set IDs collapsed to a single URL -- andbrowseGS()on an object without aurlpatterncomputedsub(NULL, ...)intocharacter(0), whichbrowseURL()silently opened nowhere. It is now an error, andgson_url()warns and returnsNAinstead. The URL comes only from what the object declares: gson ships no table of other databases' websites, so a producer that wants its gene sets browsable declares aurlpattern(read.gmt.wp()does, andReactomePA::gson_Reactome()currently passesurlpattern = NULL).set_gson(), referenced nowhere in the family, is gone (2026-10-08, Thu)GSONobjects can be subset with[:x[i]returns the gene sets selected byias aGSON. Before, taking a subset meant filtering the three mapping tables by hand --gson(x@gsid2gene[x@gsid2gene$gsid %in% ids, ])-- and keepinggsid2nameandgene2namein step withgsid2geneoneself, which is easy to get wrong in the directionvalidate_gson()reports: agene2namerow for a gene that is no longer a member of any gene set. The method prunes the three tables together, filteringgene2nameby the genes that survive, and carries every metadata slot (species,gsname,version,accessed_date,keytype,gsidtype,urlpattern,info,schema_version) over to the result. Gene set IDs, positions and logical vectors all select,x[]returnsx, and a gene set ID the object does not hold or an index out of bounds is an error that names what is missing.x[, j]is an error too, since aGSONhas no second dimension.x[[i]]is unchanged and still returns the genes of a single gene set (2026-10-08, Thu)gson_union()merges severalGSONobjects, or oneGSONList, into a single collection -- the multi-source case (GO + KEGG + REAC as one membership table) that callers used to assemble fromrbind()andunique()themselves -- and aGSONListnow survives subsetting, sincex[1]used to come back as a plain list and lostprint.GSONList. Two objects that name the same source (gsname, andgsidtypetoo once every object declares one) are the same collection: a gene set both of them carry is joined into one set holding every gene either gives it, so two files of one download merge rather than collide. A gene set ID that two *different* sources both use is refused, because the merged table would say that one ID names two sets of genes and every result read from it would be quietly wrong;.conflict = "prefix"qualifies every ID with its source (KEGG:hsa00010) and"rename"qualifies only the colliding ones, leaving IDs that already begin with their source alone, and thegsid2namerows follow the IDs they describe. Of the metadata,species,keytypeandschema_versionhave to agree -- one membership table cannot mean two organisms, two identifier types or two file schemas -- whilegsname,gsidtype,version,accessed_dateandinfoare joined with" + "when the sources name themselves differently.urlpatternis kept only when every object declares the same one and no ID moved, because a pattern that resolves part of the collection to the wrong page is worse than no pattern (2026-10-08, Thu)read.gson()says which file is wrong and what about it, instead of passing on whateverjsonliteorutils::stack()happened to notice. A.gsonfile is written by other programs as often as by this one, and a file that did not follow the format used to fail with a message about R internals rather than about the file:at least one vector element is requiredwhengsid2genewas absent,invalid 'times' valuewhen it held a JSON array of arrays,arguments imply differing number of rows: 1, 0when it held a string,$ operator is invalid for atomic vectorswhen the whole file was an array,gsid2name should have at least 2 columnswhengsid2nameheld a string instead of the two arraysgsidandname, andlexical error: invalid char in json text-- quoting the *path* of a file that does not exist -- fromjsonlite. Two malformed files read without any error at all:{"gsid2gene": {"": ["101"]}}gave a gene set with an empty ID, and{"gsid2gene": {"GS1": {"a": ["101"]}}}gaveGS1->101with the inner key silently dropped (verified against the released 0.2.1). Each case is now an error naming the file, the member and the shape found --the gson file <path> holds 'gsid2gene' as a matrix, but the memberships must be an object keyed by gene set ID, e.g. {"GS1": ["101", "102"]}-- a file that will not parse at all reportsthe gson file <path> could not be read:with the parse error beneath it, and a local path that does not exist is caught beforejsonliteis asked (a URL is not, so reading from the web still works). The shapes recognised are the onesjsonlite::fromJSON()actually produces, which simplifies an object of two equal-length arrays to adata.frameand an array of equal-length arrays to a matrix. An empty"gsid2gene": {}is no longer an error either: it is a collection of no gene sets, which is whatwrite.gson()of an emptyGSONwrites, and the two now round-trip to an identical object. Well-formed files, from this package or from another, read as before (2026-10-08, Thu)validate_gson()reports what the identifier fields claim without carrying: akeytypeorgsidtypeleft at a placeholder --UNKNOWN,NA,null,none,-, the empty string -- now readskeytype 'UNKNOWN' names no identifier type; leave it NULL when the type is not known, and agsidtypethe object's own gene set IDs contradict readsgsidtype 'KEGG' is not the prefix of the gene set IDs, which begin GO -- as in 'GO:0008150'. There is deliberately no dictionary of accepted values, which is what the same report would need to be complete:MicrobiomeProfilerwriteskegg_orthology,kegg_compound,kegg_enzyme,eggNOG_OG,taxid,cog,metabolite_idandhmdb_idaskeytype, none of them akeytypes()value of any Bioconductor annotation package, so an enumerated list held by gson would reject the collections the family itself produces and could only be kept current by hand. What gson can verify is an object against itself, so only a prefix the IDs actually carry is compared: identifiers with no prefix (WP5119,hsa00010, the numeric GO IDs some producers write) give nothing to check and are left alone, agsidtypethatgson_union()joined ("GO + KEGG") is read as the several domains it is, and IDs qualified with the source's owngsname-- what.conflict = "prefix"writes -- count as agreement, as does one domain out of several, since a partly right claim is a merged collection rather than a wrong one. Nothing is rewritten, following the unknownschema_version:keytype = "UNKNOWN"is the valueenrichit::ora_gson()andgsea_gson()give anenrichResultwhen aGSONdeclares none, so it is a value in circulation, and silently turning it into something else would change what a consumer asks an annotation package.validate_gson()throws on a report only when called with the defaulterror = TRUE, and nothing outside gson calls it (verified by searching the family); the collections this package ships stay valid,read.gmt.wp()'s bundled WikiPathways object withgsidtype = "WP"and IDs likeWP5119among them (2026-10-08, Thu)write.gson()writes the gene sets in the order the collection holds them, so a.gsonfile reads back as the object it was written from.split()sorted the gene set IDs, and a JSON object keeps the order it is written in, so every collection whose own order is not the alphabetical one came back rearranged: the bundled WikiPathways object, whosenames()beginWP5119, WP2840, WP4685, read back beginningWP100, WP106, WP107, andidentical()of the two was FALSE although all 724 gene sets and all 30629 memberships matched. The order is not decoration --names(x),print()andx[1]all follow it, so a ranked collection loses what its rank means -- and keeping it costs nothing, sincejsonlite::toJSON()writes the members of an object in the order the list holds them (2026-10-08, Thu)- An audit of this round's own changes found three gaps at the boundaries it had just added, now closed.
read.gson()refused a file holding"GS2": []with the misleading "not a flat list of identifiers" -- jsonlite reads an empty array as an empty list, so an empty gene set fell into the wrong check -- and now saysholds gene set 'GS2' with an empty gene list; a GSON cannot hold a set of no genes, and write.gson() omits such sets with a warning, since dropping the set in silence would look like the file never mentioned it. A hand-written"GS1": ["g1", null]read without complaint into a gene genuinely calledNA-- the same disease the write side skips such rows for -- and the read side now skips them on the same terms, warning how many memberships were omitted; a set whose every gene is unusable leaves a collection without it. Andvalidate_gson()silently took akeytypeorgsidtypeleft atNAfor absent: anNAslot is notNULL, and it is now reported with the values that name nothing. Files without these defects read exactly as before (2026-10-08, Thu)
Show all changes in 0.2.2Hide the rest of the changes in 0.2.2
Descriptiondescribes what a collection is used for rather than what the object holds: aGSONcan be built from data frames, read from and written to the 'gson' JSON and 'GMT' formats, subset by gene set, merged across sources, validated, and resolved to the web addresses of the databases it comes from, which is the point of the format -- a collection gathered by one package is analysed by another.URLandBugReportswere missing, so the manual pages and the CRAN page had no link out; they now follow the family convention and point at the book and atYuLab-SMU/gson. Three dependencies that no code reached are gone:rlangwas imported for a@importFrom rlang .datatag left over from thetidyr::separate()call 0.2.1 replaced withstrsplit(),utils::packageVersion()for the commented-outinfoline ofwrite.gson(), anddigestandfswere listed underSuggestswith no reference anywhere in the package or its tests. Of the three, onlyrlangwas underImportsand so was actually loaded -- animportFromdirective pulls the namespace in when gson is loaded -- whiledigestandfssat underSuggestsand cost nothing but a line of metadata. gson now starts no package its code does not call.R CMD checkis unchanged and aGSONbuilt and written before the trim reads identically after it (2026-10-08, Thu)README.Rmdis the source of the readme, andREADME.mdis what it renders, following the convention of the family: the badges, the opening paragraph and the output of every example come out of the package rather than out of memory, so the readme cannot quietly fall behind the code it describes. Thereadme:target of the Makefile already pointed at this file, which did not exist. It walks through building a collection from two data frames, the.gsonround trip
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-05
- NOTE12 OK, 1 NOTE2026-08-01
- OK14 OK2026-03-10
Documentation
- Help pages
- 12 (1 internal)includes a package overview page
- Examples that run
- 100%5 run in a plain check
- Exported functions with examples
- 71%
- Documented parameters
- 89%
- Return-value docs
- 100%
- Help pages citing references
- 0%
Downloads
Repository
- PRs merged (all time)
- 57%
- Median days to close, 50 most recently updated closed issues
- 70
- Median days to close, 50 most recently updated closed PRs
- 1
3 development-tooling and community-health practices detected across 3 families in the upstream repository
Checks run against github.com/yulab-smu/gson on 2026-10-18.
Dependencies
Installing gson adds 7 packages (6 with compiled code, 4.0 MB of source tarballs). Heaviest: fs 1.4 MB, jsonlite 1.0 MB, rlang 766 KB.
Code & Tests
People & History
Pick two releases to compare their code metrics.
- 0.2.2Latest
- 0.2.12026-08-04 · diff ↗
- RR 4.6.0 released · 2026-04-24
- RR 4.5.0 released · 2025-04-11
- RR 4.4.0 released · 2024-04-24
- RR 4.3.0 released · 2023-04-21
- 0.2.0
- 0.1.02023-03-07 · diff ↗
- 0.0.9
- 0.0.82022-08-20 · diff ↗
- 0.0.72022-08-09 · diff ↗
- 0.0.62022-06-07 · diff ↗
- 0.0.42022-04-28
- RR 4.2.0 released · 2022-04-22
Package metadata
- First published
- 2022-04-28
- Total releases
- 9 / 4 yrs
- License
- Artistic-2.0 OSI
- Download size
- 130 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("gson")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-22, which the citation names so these numbers can be found later. More on citing and the projects behind them.