diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index d3df598ad..087b1a56f 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -113,15 +113,15 @@ basic_json(basic_json&& other) noexcept; ```mermaid flowchart TD - A(["initializer_list init"]) --> B{"empty, or every element is a 2-element
array whose first element is a string?"} - B -->|"yes"| C{"type_deduction"} - B -->|"no"| D{"type_deduction"} + A(["initializer_list init"]) --> B{{"empty, or every element is a 2-element
array whose first element is a string?"}} + B -->|"yes"| C{{"type_deduction"}} + B -->|"no"| D{{"type_deduction"}} C -->|"true"| OBJ["create object"] - C -->|"false"| E{"manual_type"} + C -->|"false"| E{{"manual_type"}} E -->|"object"| OBJ E -->|"array"| ARR["create array"] D -->|"true"| ARR - D -->|"false"| F{"manual_type"} + D -->|"false"| F{{"manual_type"}} F -->|"array"| ARR F -->|"object"| ERR["throw type_error.301"] ``` diff --git a/docs/mkdocs/docs/api/basic_json/begin.md b/docs/mkdocs/docs/api/basic_json/begin.md index 72583ce57..850cf118a 100644 --- a/docs/mkdocs/docs/api/basic_json/begin.md +++ b/docs/mkdocs/docs/api/basic_json/begin.md @@ -7,7 +7,9 @@ const_iterator begin() const noexcept; Returns an iterator to the first element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/cbegin.md b/docs/mkdocs/docs/api/basic_json/cbegin.md index b4c3e588f..2c200829a 100644 --- a/docs/mkdocs/docs/api/basic_json/cbegin.md +++ b/docs/mkdocs/docs/api/basic_json/cbegin.md @@ -6,7 +6,9 @@ const_iterator cbegin() const noexcept; Returns an iterator to the first element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/cend.md b/docs/mkdocs/docs/api/basic_json/cend.md index fb568762a..7bc8adcfc 100644 --- a/docs/mkdocs/docs/api/basic_json/cend.md +++ b/docs/mkdocs/docs/api/basic_json/cend.md @@ -6,7 +6,9 @@ const_iterator cend() const noexcept; Returns an iterator to one past the last element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/crbegin.md b/docs/mkdocs/docs/api/basic_json/crbegin.md index 545f86044..ab7562cfa 100644 --- a/docs/mkdocs/docs/api/basic_json/crbegin.md +++ b/docs/mkdocs/docs/api/basic_json/crbegin.md @@ -6,7 +6,9 @@ const_reverse_iterator crbegin() const noexcept; Returns an iterator to the reverse-beginning; that is, the last element. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/crend.md b/docs/mkdocs/docs/api/basic_json/crend.md index 56cc40c52..d23e41b31 100644 --- a/docs/mkdocs/docs/api/basic_json/crend.md +++ b/docs/mkdocs/docs/api/basic_json/crend.md @@ -7,7 +7,9 @@ const_reverse_iterator crend() const noexcept; Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder, attempting to access it results in undefined behavior. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/end.md b/docs/mkdocs/docs/api/basic_json/end.md index e94ec78a2..19b1441e2 100644 --- a/docs/mkdocs/docs/api/basic_json/end.md +++ b/docs/mkdocs/docs/api/basic_json/end.md @@ -7,7 +7,9 @@ const_iterator end() const noexcept; Returns an iterator to one past the last element. -![Illustration from cppreference.com](../../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/parse_event_t.md b/docs/mkdocs/docs/api/basic_json/parse_event_t.md index f8c12eff8..14f5840a6 100644 --- a/docs/mkdocs/docs/api/basic_json/parse_event_t.md +++ b/docs/mkdocs/docs/api/basic_json/parse_event_t.md @@ -22,7 +22,9 @@ The parser callback distinguishes the following events: ## Examples -![Example when certain parse events are triggered](../../images/callback_events.png) +
+--8<-- "images/callback_events.svg" +
??? example diff --git a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md index 727905e5a..52bc2a595 100644 --- a/docs/mkdocs/docs/api/basic_json/parser_callback_t.md +++ b/docs/mkdocs/docs/api/basic_json/parser_callback_t.md @@ -23,7 +23,9 @@ table describes the values of the parameters `depth`, `event`, and `parsed`. | `parse_event_t::array_end` | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array | | `parse_event_t::value` | the parser finished reading a JSON value | depth of the value | the parsed JSON value | -![Example when certain parse events are triggered](../../images/callback_events.png) +
+--8<-- "images/callback_events.svg" +
Discarding a value (i.e., returning `#!cpp false`) has different effects depending on the context in which function was called: diff --git a/docs/mkdocs/docs/api/basic_json/rbegin.md b/docs/mkdocs/docs/api/basic_json/rbegin.md index 544b8a643..c625211f1 100644 --- a/docs/mkdocs/docs/api/basic_json/rbegin.md +++ b/docs/mkdocs/docs/api/basic_json/rbegin.md @@ -7,7 +7,9 @@ const_reverse_iterator rbegin() const noexcept; Returns an iterator to the reverse-beginning; that is, the last element. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
## Return value diff --git a/docs/mkdocs/docs/api/basic_json/rend.md b/docs/mkdocs/docs/api/basic_json/rend.md index 12f68e787..668247fea 100644 --- a/docs/mkdocs/docs/api/basic_json/rend.md +++ b/docs/mkdocs/docs/api/basic_json/rend.md @@ -8,7 +8,9 @@ const_reverse_iterator rend() const noexcept; Returns an iterator to the reverse-end; that is, one before the first element. This element acts as a placeholder, attempting to access it results in undefined behavior. -![Illustration from cppreference.com](../../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
## Return value diff --git a/docs/mkdocs/docs/css/brand.css b/docs/mkdocs/docs/css/brand.css new file mode 100644 index 000000000..5aefef421 --- /dev/null +++ b/docs/mkdocs/docs/css/brand.css @@ -0,0 +1,739 @@ +/* nlohmann/json brand for Material for MkDocs (values from brand/kit/tokens.css) */ + +@font-face { + font-family: "Inter"; + src: url("fonts/Inter.woff2") format("woff2"); + font-weight: 100 900; + font-display: swap; +} +@font-face { + font-family: "Inter"; + src: url("fonts/Inter-Italic.woff2") format("woff2"); + font-style: italic; + font-weight: 100 900; + font-display: swap; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/JetBrainsMono.woff2") format("woff2"); + font-weight: 100 800; + font-display: swap; +} +@font-face { + font-family: "JetBrains Mono"; + src: url("fonts/JetBrainsMono-Italic.woff2") format("woff2"); + font-style: italic; + font-weight: 400; + font-display: swap; +} + +:root { + --md-text-font: "Inter"; + --md-code-font: "JetBrains Mono"; +} + +/* light */ +[data-md-color-scheme="default"] { + --nj-blue: #1747a6; + --nj-blue-deep: #10357d; + --nj-blue-soft: #eaf1fd; + --nj-band: #1747a6; + --nj-red: #d93a3f; + --nj-red-soft: #fdeced; + --nj-string: #b4322a; + --nj-comment: #646d7a; + + --md-primary-fg-color: var(--nj-band); + --md-primary-fg-color--light: #1d55c4; + --md-primary-fg-color--dark: #10357d; + --md-accent-fg-color: var(--nj-blue-deep); + --md-accent-fg-color--transparent: rgba(23, 71, 166, .1); + --md-typeset-a-color: var(--nj-blue); + --md-default-fg-color: #0f1419; + --md-default-fg-color--light: #4a515c; + --md-default-fg-color--lighter: #8a919c; + --md-default-fg-color--lightest: #e3e6eb; + --md-typeset-color: #0f1419; + --md-code-bg-color: #f5f7fa; + --md-code-fg-color: #0f1419; +} + +/* dark */ +[data-md-color-scheme="slate"] { + --nj-blue: #7aa7f5; + --nj-blue-deep: #a9c6f8; + --nj-blue-soft: #16233b; + --nj-band: #173e8c; + --nj-red: #f06a6e; + --nj-red-soft: #3a1c1e; + --nj-string: #f08a7e; + --nj-comment: #8b929e; + + --md-hue: 220; + --md-primary-fg-color: var(--nj-band); + --md-primary-fg-color--light: #163f93; + --md-primary-fg-color--dark: #0c2556; + --md-accent-fg-color: var(--nj-blue-deep); + --md-accent-fg-color--transparent: rgba(122, 167, 245, .12); + --md-typeset-a-color: var(--nj-blue); + --md-default-bg-color: #0f1218; + --md-default-fg-color: #f2f4f7; + --md-default-fg-color--light: #b3b9c4; + --md-default-fg-color--lighter: #7b828e; + --md-default-fg-color--lightest: #272c36; + --md-typeset-color: #f2f4f7; + --md-code-bg-color: #171b23; + --md-code-fg-color: #f2f4f7; + --md-footer-bg-color: #0b0e13; + --md-footer-bg-color--dark: #08090d; +} + +/* syntax colours (all at least 4.5:1 on the code panel in both schemes) */ +[data-md-color-scheme="default"] { + --nj-hl-keyword: #1747a6; + --nj-hl-type: #0d6e72; + --nj-hl-function: #6b3fb8; + --nj-hl-string: #23793a; + --nj-hl-escape: #0f5d2a; + --nj-hl-key: #10357d; + --nj-hl-number: #b04a07; + --nj-hl-preproc: #a3247a; +} +[data-md-color-scheme="slate"] { + --nj-hl-keyword: #7aa7f5; + --nj-hl-type: #4fd1c5; + --nj-hl-function: #c4a3ff; + --nj-hl-string: #7ed491; + --nj-hl-escape: #b2ecc0; + --nj-hl-key: #a9c6f8; + --nj-hl-number: #f5a65b; + --nj-hl-preproc: #f48fc8; +} +.md-typeset .highlight :is(.k, .kd, .kn, .kr, .kp, .kv, .ow) { color: var(--nj-hl-keyword); font-weight: 600; } +.md-typeset .highlight :is(.kt, .nc, .nn, .na) { color: var(--nj-hl-type); } +.md-typeset .highlight :is(.nf, .fm, .nb, .bp) { color: var(--nj-hl-function); } +.md-typeset .highlight :is(.s, .s1, .s2, .sb, .sc, .sd, .sh, .sx, .sr, .ss, .sa, .dl, .cpf) { color: var(--nj-hl-string); } +.md-typeset .highlight :is(.se, .si) { color: var(--nj-hl-escape); font-weight: 600; } +.md-typeset .highlight .nt { color: var(--nj-hl-key); } +.md-typeset .highlight :is(.m, .mi, .mf, .mh, .mo, .mb, .il, .kc) { color: var(--nj-hl-number); } +.md-typeset .highlight .kc { font-weight: 500; } +.md-typeset .highlight .cp { color: var(--nj-hl-preproc); } +.md-typeset .highlight :is(.c, .c1, .cm, .ch, .cs) { color: var(--nj-comment); font-style: italic; } +.md-typeset .highlight :is(.o, .p) { color: var(--md-default-fg-color--light); } +.md-typeset .highlight :is(.n, .nx, .nv, .no, .ni, .nl, .py) { color: var(--md-code-fg-color); } +.md-typeset .highlight .err { color: inherit; background: none; } + +/* typography: Inter with cv11/ss03, bold headlines with negative tracking */ +body { + font-feature-settings: "cv11", "ss03"; +} +.md-typeset { + font-size: .85rem; /* 17 px body */ + line-height: 1.65; +} +.md-typeset h1 { + color: var(--md-typeset-color); + font-weight: 800; + letter-spacing: -.035em; + line-height: 1.1; +} +.md-typeset h2 { + font-weight: 800; + letter-spacing: -.025em; + line-height: 1.15; +} +.md-typeset h3 { + font-weight: 700; + letter-spacing: -.015em; +} +.md-typeset table:not([class]) { + font-variant-numeric: tabular-nums; +} + +/* header and tabs: one blue band with the faint syntax pattern; wordmark instead of the site name. + A veil over the tile keeps the glyphs from running through the navigation text; the hero shows the pattern at full strength. */ +.md-header, .md-tabs { + --nj-veil: linear-gradient(color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent), color-mix(in srgb, var(--md-primary-fg-color) 62%, transparent)); + background-color: var(--md-primary-fg-color); + background-image: var(--nj-veil), url("pattern-on-blue.svg"); + background-size: auto, 160px 160px; + background-position: 0 0, 0 0; +} +.md-tabs { + background-position: 0 0, 0 -48px; /* continue the header's tile */ +} +.md-header__button.md-logo img { + width: auto; + height: 1.5rem; +} +.md-header__topic:first-child .md-ellipsis { /* the wordmark replaces the site name at every width */ + display: none; +} +@media screen and (max-width: 76.234375em) { /* Material hides the header logo here: show the wordmark on phones, too */ + .md-header__button.md-logo { + display: block; + margin: 0 0 0 .2rem; + padding: .4rem .2rem; + } + .md-header__button[for="__drawer"] { + order: -1; + } +} +.md-nav__button.md-logo img { + width: auto; + height: 1.4rem; +} + +/* shapes: 6 px corners on blocks, 4 px on inline code */ +.md-typeset pre > code, .md-typeset .highlight, .md-typeset .highlighttable, +.md-typeset .admonition, .md-typeset details, .md-typeset .tabbed-set, +.md-typeset .grid.cards > ul > li, .md-typeset .md-button { + border-radius: 6px; +} +.md-typeset code { + border-radius: 4px; +} + +/* inline code: the font alone marks it, no grey box; linked code keeps the link colour */ +.md-typeset :not(pre) > code { + padding: 0; + background-color: transparent; + box-shadow: none; + color: inherit; + font-size: .88em; +} +.md-typeset .highlight pre > code, .md-typeset .highlighttable { + border: 1px solid var(--md-default-fg-color--lightest); +} + +/* callouts: 3 px left border; note/info/tip in blue on blue-soft, danger/bug in red on red-soft */ +.md-typeset .admonition, .md-typeset details { + border-width: 0 0 0 3px; + box-shadow: none; + font-size: .8rem; +} +.md-typeset :is(.admonition, details):is(.note, .info, .tip, .abstract, .example, .question) { + border-color: var(--nj-blue); + background-color: var(--nj-blue-soft); +} +.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary) { + background-color: transparent; +} +.md-typeset :is(.note, .info, .tip, .abstract, .example, .question) > :is(.admonition-title, summary)::before { + background-color: var(--nj-blue); +} +.md-typeset :is(.admonition, details):is(.danger, .bug, .failure) { + border-color: var(--nj-red); + background-color: var(--nj-red-soft); +} +.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary) { + background-color: transparent; +} +.md-typeset :is(.danger, .bug, .failure) > :is(.admonition-title, summary)::before { + background-color: var(--nj-red); +} + +/* buttons */ +.md-typeset .md-button--primary { + background-color: var(--nj-blue); + border-color: var(--nj-blue); +} + +/* home page hero (overrides/home.html): blue band with the syntax pattern, headline, lead, buttons, code panel */ +.nj-hero { + --nj-hero-to: #123a8a; + color: #fff; + background-color: var(--md-primary-fg-color); + background-image: url("pattern-on-blue.svg"), linear-gradient(160deg, var(--md-primary-fg-color), var(--nj-hero-to)); + background-size: 160px 160px, auto; + background-position: 0 -96px, 0 0; /* continue the tile of header and tabs */ +} +[data-md-color-scheme="slate"] .nj-hero { + --nj-hero-to: #0c2556; +} +.nj-hero__inner { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); + gap: 2.4rem; + align-items: center; + padding: 3rem .8rem 3.6rem; +} +.nj-hero__kicker { + display: inline-block; + margin: 0; + padding: .15rem .55rem; + border: 1px solid rgba(255, 255, 255, .35); + border-radius: 4px; + color: #bcd3fb; + font-family: var(--md-code-font-family); + font-size: .65rem; /* 13 px */ + font-weight: 500; + letter-spacing: .02em; +} +.nj-hero__title { + margin: 0 0 .8rem; + color: #fff; + font-size: clamp(1.7rem, 4.2vw, 3.2rem); /* 34-64 px */ + font-weight: 800; + line-height: 1.02; + letter-spacing: -.035em; +} +.nj-hero__title span { + color: #bcd3fb; +} +.nj-hero__lead { + max-width: 22em; + margin: 0 0 2rem; + color: rgba(255, 255, 255, .9); + font-size: clamp(1.1rem, 2.2vw, 1.6rem); /* 22-32 px */ + font-style: italic; + font-weight: 500; + line-height: 1.35; + letter-spacing: -.015em; +} +.nj-hero__actions { + display: flex; + flex-wrap: wrap; + gap: .6rem; + margin: 0; +} +.nj-hero .md-button.nj-hero__button { + margin: 0; + padding: .55em 1.3em; + border: 1px solid rgba(255, 255, 255, .6); + border-radius: 6px; + color: #fff; + font-size: .75rem; + font-weight: 600; +} +.nj-hero .md-button--primary.nj-hero__button { + border-color: #fff; + background-color: #fff; + color: var(--md-primary-fg-color); +} +.nj-hero .md-button.nj-hero__button:is(:hover, :focus) { + border-color: #fff; + background-color: rgba(255, 255, 255, .12); + color: #fff; +} +.nj-hero .md-button--primary.nj-hero__button:is(:hover, :focus) { + background-color: #eaf1fd; + color: var(--md-primary-fg-color); +} +.nj-hero__code { + border-radius: 6px; + background-color: var(--md-code-bg-color); + box-shadow: 0 12px 32px rgba(5, 18, 48, .35); +} +.nj-hero__code pre { + margin: 0; + padding: 1.1rem 1.3rem; + overflow-x: auto; + color: var(--md-code-fg-color); + font-family: var(--md-code-font-family); + font-size: .7rem; /* 14 px */ + line-height: 1.7; +} +.nj-hero__code .nj-k { color: var(--nj-hl-type); } +.nj-hero__code .nj-s { color: var(--nj-hl-string); } +.nj-hero__code .nj-n { color: var(--nj-hl-number); } +.nj-hero__code .nj-c { color: var(--nj-comment); font-style: italic; } +@media screen and (max-width: 59.984375em) { + .nj-hero__inner { + grid-template-columns: minmax(0, 1fr); + gap: 1.8rem; + padding: 2rem .8rem 2.4rem; + } +} +@media screen and (max-width: 29.984375em) { + .nj-hero__code pre { + font-size: .6rem; + } +} + +/* the hero carries the headline: keep the page's h1 for the title and screen readers only */ +body:has(.nj-hero) .md-content__inner > h1 { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; +} + +/* cards: 1 px line border, 6 px corners, blue icon on soft blue, blue border on hover */ +.md-typeset .grid.cards > ul > li { + padding: 1rem 1.1rem; + border: 1px solid var(--md-default-fg-color--lightest); + background-color: var(--md-default-bg-color); + transition: border-color 150ms, box-shadow 150ms, transform 150ms; +} +.md-typeset .grid.cards > ul > li:is(:hover, :focus-within) { + border-color: var(--nj-blue); + box-shadow: 0 6px 18px rgba(23, 71, 166, .12); + transform: translateY(-1px); +} +.md-typeset .grid.cards > ul > li > hr { + display: none; +} +.md-typeset .grid.cards .twemoji.lg { + box-sizing: content-box; + margin-right: .5rem; + padding: .3rem; + border-radius: 6px; + background-color: var(--nj-blue-soft); + color: var(--nj-blue); +} +.md-typeset .grid.cards > ul > li > p:first-child strong { + font-size: .9rem; + font-weight: 700; + letter-spacing: -.01em; +} +.md-typeset .grid.cards > ul > li > p:last-child a { + font-weight: 600; +} +@media (prefers-reduced-motion: reduce) { + .md-typeset .grid.cards > ul > li { transition: none; transform: none !important; } +} + +/* home page: no table of contents, so keep the text at reading width and the cards in three columns */ +body:has(.nj-hero) .md-content { + max-width: 58rem; + margin: 0 auto; +} +@media screen and (min-width: 60em) { + body:has(.nj-hero) .md-typeset .grid.cards { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +/* lists: Material's indent, bullets as small blue squares, numbers in the secondary text colour */ +.md-typeset ul:not(.task-list, .md-annotation-list) > li { + position: relative; + list-style: none; +} +.md-typeset ul:not(.task-list, .md-annotation-list) > li::before { + content: ""; + position: absolute; + top: .65em; /* middle of the first line at line-height 1.65 */ + left: -.95em; + width: .36em; + height: .36em; + background-color: var(--nj-blue); +} +.md-typeset ol > li::marker { + color: var(--md-default-fg-color--light); + font-variant-numeric: tabular-nums; +} +.md-typeset .grid.cards > ul > li::before { + display: none; +} + +/* wider page: Material's grid stops at 61rem (1220 px) */ +.md-grid { + max-width: 72rem; /* 1440 px */ +} + +/* tables: full width, 1 px line border with 6 px corners, header row on the panel colour, 15 px text */ +.md-typeset__table { + display: block; /* Material: inline-block, so tables only grow to their content */ +} +.md-typeset table:not([class]) { + display: table; + width: 100%; + border: 1px solid var(--md-default-fg-color--lightest); + border-radius: 6px; + font-size: .75rem; + line-height: 1.55; +} +.md-typeset table:not([class]) th { + padding: .65em 1em; + border-bottom: 1px solid var(--md-default-fg-color--lightest); + background-color: var(--md-code-bg-color); + color: var(--md-typeset-color); + font-weight: 600; + vertical-align: bottom; +} +.md-typeset table:not([class]) th:first-child { border-top-left-radius: 5px; } +.md-typeset table:not([class]) th:last-child { border-top-right-radius: 5px; } +.md-typeset table:not([class]) td { + padding: .65em 1em; + border-top: 1px solid var(--md-default-fg-color--lightest); +} +.md-typeset table:not([class]) tbody tr:first-child td { + border-top: 0; +} +.md-typeset table:not([class]) tbody tr:hover { + background-color: var(--nj-blue-soft); + box-shadow: none; +} +.md-typeset table:not([class]) td code { + white-space: nowrap; +} +/* numbers and header code stay on one line; a table wider than the screen scrolls sideways instead */ +.md-typeset table:not([class]) :is(td, th):is([align="right"], [style*="right"]), +.md-typeset table:not([class]) th code { + white-space: nowrap; +} +@media screen and (max-width: 44.984375em) { + .md-typeset table:not([class]) { + font-size: .7rem; + } + .md-typeset table:not([class]) :is(th, td) { + padding: .55em .75em; + } +} + +/* more room before headings (Material: h2 1.6em, h3 1.6em, h4-h6 1-1.25em) and at the end of each page (1.2rem) */ +.md-typeset h2 { margin-top: 2.4em; } +.md-typeset h3 { margin-top: 2.2em; } +.md-typeset h2 + h3 { margin-top: .8em; } +.md-typeset :is(h4, h5, h6) { margin-top: 1.8em; } +.md-content__inner { + margin-bottom: 4.8rem; /* 96 px */ +} + +/* Mermaid diagrams: white boxes with a blue outline, grey lines, Inter; shapes and spacing in js/mermaid-brand.js */ +:root > * { + --md-mermaid-font-family: var(--md-text-font-family), sans-serif; + --md-mermaid-edge-color: var(--md-default-fg-color--lighter); + --md-mermaid-node-bg-color: var(--md-default-bg-color); + --md-mermaid-node-fg-color: var(--nj-blue); + --md-mermaid-label-bg-color: var(--md-default-bg-color); + --md-mermaid-label-fg-color: var(--md-default-fg-color); + --md-mermaid-sequence-actor-bg-color: var(--nj-blue-soft); + --md-mermaid-sequence-actor-line-color: var(--md-default-fg-color--lighter); + --md-mermaid-sequence-note-bg-color: var(--md-code-bg-color); + --md-mermaid-sequence-note-border-color: var(--md-default-fg-color--lightest); + --md-mermaid-sequence-number-fg-color: #fff; +} + +/* a diagram wider than the column scrolls sideways (js/mermaid-brand.js stops shrinking it at 80 %) */ +.md-typeset .mermaid { + overflow-x: auto; +} + + +/* ---- polish pass ---- */ + +/* header: a search field that reads as a field on the band, tabs with an active marker, readable GitHub facts */ +.md-header .md-search__form { + border: 1px solid rgba(255, 255, 255, .22); + border-radius: 6px; + background-color: rgba(255, 255, 255, .12); + transition: background-color 150ms, border-color 150ms; +} +.md-header .md-search__form:hover { + border-color: rgba(255, 255, 255, .4); + background-color: rgba(255, 255, 255, .2); +} +.md-search__output { + border-radius: 0 0 6px 6px; + box-shadow: 0 16px 40px rgba(5, 18, 48, .28); +} +.md-search-result__link:focus-visible, .md-search-result__more summary:focus-visible { + outline: 2px solid var(--nj-blue); + outline-offset: -2px; +} +.md-tabs__link { + font-size: .72rem; + font-weight: 500; + opacity: .85; +} +.md-tabs__item--active .md-tabs__link, .md-tabs__link:hover { + opacity: 1; +} +.md-tabs__item--active { + box-shadow: inset 0 -2px 0 #fff; +} +.md-source__facts { + font-size: .62rem; +} + +/* footer: deep brand blue instead of neutral black (the dark scheme keeps its own near-black) */ +[data-md-color-scheme="default"] { + --md-footer-bg-color: #0d2a66; + --md-footer-bg-color--dark: #0a2152; +} +.md-footer-meta .md-copyright, .md-footer-meta .md-copyright a { + color: rgba(255, 255, 255, .72); +} +.md-footer__link { + border-radius: 6px; + transition: background-color 150ms; +} +.md-footer__link:is(:hover, :focus-visible) { + background-color: rgba(255, 255, 255, .08); +} + +/* page title: tighter to the first paragraph; page actions (view/edit source) stay in the background */ +.md-typeset h1 { + margin-bottom: 1rem; +} +.md-content__button { + opacity: .55; + transition: opacity 150ms; +} +.md-content__button:is(:hover, :focus-visible) { + opacity: 1; +} +body:has(.nj-hero) .md-content__button { + display: none; +} + +/* sidebars: bold marker for the current page, rail and marker for the table of contents */ +.md-nav__link--active { + font-weight: 600; +} +.md-nav--secondary > .md-nav__list { + margin-left: -.1rem; + border-left: 1px solid color-mix(in srgb, var(--md-default-fg-color) 16%, transparent); +} +.md-nav--secondary .md-nav__link { + margin-left: -1px; + padding-left: .6rem; + border-left: 2px solid transparent; +} +.md-nav--secondary .md-nav__link--active { + border-left-color: var(--nj-blue); + color: var(--nj-blue); +} +.md-nav--secondary .md-nav__list .md-nav__list { + padding-left: .6rem; +} +.md-nav--secondary .md-nav__title { + font-size: .64rem; + letter-spacing: .04em; + text-transform: uppercase; +} + +/* keyboard focus: one visible ring in the brand blue */ +.md-typeset a:focus-visible, .md-typeset .md-button:focus-visible, .md-typeset summary:focus-visible, +.md-nav__link:focus-visible, .md-tabs__link:focus-visible, .md-top:focus-visible { + outline: 2px solid var(--nj-blue); + outline-offset: 2px; + border-radius: 3px; +} +.md-header :is(a, button, label):focus-visible, .nj-hero a:focus-visible { + outline: 2px solid #fff; + outline-offset: 2px; +} + +/* inline code must not stretch the line box (JetBrains Mono has tall metrics), abbreviations stay quiet */ +.md-typeset :not(pre) > code { + line-height: 1; +} +.md-typeset abbr { + border-bottom-color: var(--md-default-fg-color--lighter); +} +.md-typeset :is(h1, h2, h3, h4, a, .grid.cards > ul > li > p:first-child) abbr { + border-bottom-color: transparent; +} + +/* number railroad diagram: inline SVG (docs/images/json_syntax_number.svg), follows the colour scheme */ +.md-typeset .railroad-wrap, .md-typeset .nj-fig-wrap { + overflow-x: auto; + margin: 1.5rem 0; +} +.md-typeset svg.railroad { + display: block; + width: 100%; + min-width: 36rem; + max-width: 52rem; + margin-inline: auto; + color: var(--md-default-fg-color); + font-family: var(--md-text-font-family), sans-serif; +} +.railroad .rr-t { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, transparent); stroke: var(--md-typeset-a-color); stroke-width: 2; } +.railroad .rr-track, .railroad .rr-n { stroke: color-mix(in srgb, currentColor 60%, var(--md-default-bg-color)); stroke-opacity: 1; } /* opaque, so overlapping segments do not darken */ +.railroad .rr-n { fill: color-mix(in srgb, currentColor 6%, transparent); stroke-width: 2; } +.railroad .rr-bg { fill: var(--md-default-bg-color); } +.railroad .rr-arrow { fill: currentColor; fill-opacity: .6; } +.railroad text { fill: currentColor; text-anchor: middle; } +.railroad .rr-c { font-family: var(--md-code-font-family), monospace; font-size: 16px; font-weight: 500; } +.railroad .rr-b { font-size: 14.5px; font-weight: 500; } +.railroad .rr-l { font-size: 14px; text-anchor: start; fill-opacity: .6; } +.railroad .rr-name { font-size: 17px; font-weight: 700; fill-opacity: 1; } +/* figures drawn as inline SVG (docs/images/callback_events.svg, range-*.svg): they follow the colour scheme */ +.md-typeset svg.nj-fig { + display: block; + width: 100%; + height: auto; + margin-inline: auto; + color: var(--md-default-fg-color); + font-family: var(--md-text-font-family), sans-serif; +} +.md-typeset svg.nj-fig--range { min-width: 30rem; max-width: 40rem; } +.md-typeset svg.nj-fig--cb { max-width: 30rem; } +.nj-fig { --fg-line: color-mix(in srgb, currentColor 55%, var(--md-default-bg-color)); } +.nj-fig .fg-cell { fill: color-mix(in srgb, var(--md-typeset-a-color) 14%, var(--md-default-bg-color)); stroke: var(--fg-line); stroke-width: 1.5; } +.nj-fig .fg-cell--end { fill: none; stroke-dasharray: 4 3; } +.nj-fig .fg-pill { fill: color-mix(in srgb, var(--md-typeset-a-color) 10%, var(--md-default-bg-color)); stroke: var(--md-typeset-a-color); stroke-width: 1.5; } +.nj-fig .fg-pilltext { fill: var(--md-typeset-a-color); font-family: var(--md-code-font-family), monospace; font-size: 14px; font-weight: 500; text-anchor: middle; } +.nj-fig .fg-arrow { stroke: var(--fg-line); stroke-width: 1.5; fill: none; } +.nj-fig .fg-arrow--dash { stroke-dasharray: 3 3; } +.nj-fig .fg-head { fill: var(--fg-line); } +.nj-fig .fg-note { fill: currentColor; fill-opacity: .7; font-size: 13.5px; text-anchor: middle; } +.nj-fig .fg-note--r { text-anchor: end; } +.nj-fig .fg-note--l { text-anchor: start; } +.nj-fig .fg-h { fill: currentColor; fill-opacity: .7; font-size: 15px; font-weight: 600; } +.nj-fig .fg-code { fill: currentColor; font-family: var(--md-code-font-family), monospace; font-size: 16px; white-space: pre; } +.nj-fig .fg-leg { font-size: 15px; } +.nj-fig { --cb-os: #4eb05a; --cb-oe: #1a6b34; --cb-k: #e5534b; --cb-as: #f0c000; --cb-ae: #b07d00; --cb-v: #1f6fd1; } +[data-md-color-scheme="slate"] .nj-fig { --cb-os: #7ee787; --cb-oe: #2ea043; --cb-k: #ff7b72; --cb-as: #f2cc60; --cb-ae: #c69026; --cb-v: #58a6ff; } +.nj-fig .cb-os { fill: var(--cb-os); } +.nj-fig .cb-oe { fill: var(--cb-oe); } +.nj-fig .cb-k { fill: var(--cb-k); } +.nj-fig .cb-as { fill: var(--cb-as); } +.nj-fig .cb-ae { fill: var(--cb-ae); } +.nj-fig .cb-v { fill: var(--cb-v); } +.md-typeset img[src*="json-light"], .md-typeset img[src*="json-dark"] { + border-radius: 8px; +} +.md-typeset img { + max-width: 100%; + height: auto; +} + +/* home page: content uses the same column as every other page */ +body:has(.nj-hero) .md-content { + max-width: none; + margin: 0; +} +.md-typeset .grid.cards { + width: auto; +} + +/* 404 page (overrides/404.html) */ +.nj-404 { + max-width: 30rem; + padding: 2rem 0 3rem; +} +.nj-404__code { + margin: 0 0 .4rem; + color: var(--nj-blue); + font-family: var(--md-code-font-family); + font-size: .8rem; + font-weight: 500; +} +.md-typeset .nj-404 h1 { + margin-bottom: .8rem; +} +.nj-404 .md-button { + margin: .4rem .4rem 0 0; +} + +/* touch targets and phones */ +@media screen and (max-width: 59.984375em) { + .md-nav__link { + padding-block: .2rem; + } +} +@media screen and (max-width: 29.984375em) { + .md-typeset h1 { + font-size: 1.7em; + } + .md-typeset .highlight pre > code { + font-size: .68rem; + } +} diff --git a/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 new file mode 100644 index 000000000..b3530f3f5 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter-Italic.woff2 differ diff --git a/docs/mkdocs/docs/css/fonts/Inter.woff2 b/docs/mkdocs/docs/css/fonts/Inter.woff2 new file mode 100644 index 000000000..21d9a7f9e Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/Inter.woff2 differ diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 new file mode 100644 index 000000000..d60c270e8 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono-Italic.woff2 differ diff --git a/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 new file mode 100644 index 000000000..6b604cec6 Binary files /dev/null and b/docs/mkdocs/docs/css/fonts/JetBrainsMono.woff2 differ diff --git a/docs/mkdocs/docs/css/pattern-on-blue.svg b/docs/mkdocs/docs/css/pattern-on-blue.svg new file mode 100644 index 000000000..1c3c16de3 --- /dev/null +++ b/docs/mkdocs/docs/css/pattern-on-blue.svg @@ -0,0 +1 @@ +{]:[,}:{ diff --git a/docs/mkdocs/docs/features/arbitrary_types.md b/docs/mkdocs/docs/features/arbitrary_types.md index 9d918506f..4ead2aa4a 100644 --- a/docs/mkdocs/docs/features/arbitrary_types.md +++ b/docs/mkdocs/docs/features/arbitrary_types.md @@ -124,20 +124,20 @@ For all the macros, the first parameter is the name of the class/struct. The `DE ```mermaid flowchart TD - A["choosing a NLOHMANN_DEFINE_* macro"] --> B{"adding fields to a base class?"} + A["choosing a NLOHMANN_DEFINE_* macro"] --> B{{"adding fields to a base class?"}} B -->|"yes"| C["...DERIVED_TYPE..."] B -->|"no"| D["...TYPE..."] - C --> E{"need access to private members?"} + C --> E{{"need access to private members?"}} D --> E E -->|"yes"| F["...INTRUSIVE... (used inside the class)"] E -->|"no"| G["...NON_INTRUSIVE... (used in the namespace)"] - F --> H{"only serializing, never parsing back?"} + F --> H{{"only serializing, never parsing back?"}} G --> H H -->|"yes"| I["...ONLY_SERIALIZE"] - H -->|"no"| J{"allow missing keys when parsing?"} + H -->|"no"| J{{"allow missing keys when parsing?"}} J -->|"yes"| K["...WITH_DEFAULT"] J -->|"no"| L["plain (missing keys throw)"] - I --> M{"need custom JSON key names?"} + I --> M{{"need custom JSON key names?"}} K --> M L --> M M -->|"yes"| N["...WITH_NAMES"] diff --git a/docs/mkdocs/docs/features/element_access/index.md b/docs/mkdocs/docs/features/element_access/index.md index 4ec625a20..bb1029715 100644 --- a/docs/mkdocs/docs/features/element_access/index.md +++ b/docs/mkdocs/docs/features/element_access/index.md @@ -14,11 +14,11 @@ the value, or `end()` if it is not found). ```mermaid flowchart TD - A["accessing a value"] --> B{"must it exist?"} + A["accessing a value"] --> B{{"must it exist?"}} B -->|"yes, missing is an error"| C["at() -- throws"] B -->|"yes, but checking is my job"| D["operator[] -- unchecked"] B -->|"no, a fallback is fine"| E["value() -- default value"] - A --> F{"just testing first?"} + A --> F{{"just testing first?"}} F -->|"yes"| G["contains() / find()"] ``` diff --git a/docs/mkdocs/docs/features/iterators.md b/docs/mkdocs/docs/features/iterators.md index de493dd72..4523d2775 100644 --- a/docs/mkdocs/docs/features/iterators.md +++ b/docs/mkdocs/docs/features/iterators.md @@ -9,7 +9,9 @@ As for other containers, [`begin()`](../api/basic_json/begin.md) returns an iter placeholder and cannot be dereferenced. In case of null values, empty arrays, or empty objects, `begin()` will return `end()`. -![Illustration from cppreference.com](../images/range-begin-end.svg) +
+--8<-- "images/range-begin-end.svg" +
### Iteration order for objects @@ -112,7 +114,9 @@ for (auto& [key, val] : j_object.items()) [`rbegin()`](../api/basic_json/rbegin.md) and [`rend()`](../api/basic_json/rend.md) return iterators in the reverse sequence. -![Illustration from cppreference.com](../images/range-rbegin-rend.svg) +
+--8<-- "images/range-rbegin-rend.svg" +
??? example "Example: reverse iteration with `rbegin()` and `rend()`" diff --git a/docs/mkdocs/docs/features/types/number_handling.md b/docs/mkdocs/docs/features/types/number_handling.md index d11ad2747..d5e4ca6a5 100644 --- a/docs/mkdocs/docs/features/types/number_handling.md +++ b/docs/mkdocs/docs/features/types/number_handling.md @@ -27,7 +27,9 @@ JSON defines the syntax of numbers as follows: The following railroad diagram from [json.org](https://json.org) visualizes the number syntax: -![Syntax for JSON numbers](../../images/json_syntax_number.png) +
+--8<-- "images/json_syntax_number.svg" +
### Number interoperability @@ -69,9 +71,9 @@ otherwise, it uses unsigned integer storage. ```mermaid flowchart TD - A["number literal"] --> B{"has a fraction (.) or exponent (e/E)?"} + A["number literal"] --> B{{"has a fraction (.) or exponent (e/E)?"}} B -->|"yes"| F["number_float_t"] - B -->|"no"| C{"has a leading minus sign?"} + B -->|"no"| C{{"has a leading minus sign?"}} C -->|"yes"| D["try number_integer_t"] C -->|"no"| E["try number_unsigned_t"] D -->|"overflow"| F diff --git a/docs/mkdocs/docs/images/callback_events.png b/docs/mkdocs/docs/images/callback_events.png deleted file mode 100644 index 09aa2b383..000000000 Binary files a/docs/mkdocs/docs/images/callback_events.png and /dev/null differ diff --git a/docs/mkdocs/docs/images/callback_events.svg b/docs/mkdocs/docs/images/callback_events.svg new file mode 100644 index 000000000..544bf4f6a --- /dev/null +++ b/docs/mkdocs/docs/images/callback_events.svg @@ -0,0 +1,39 @@ + +Example of the parse events a callback sees for the J​SON text {"name": "Berlin", "location": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete. +J​SON to parse +callback events +{ + +"name" + +: "Berlin" + +, +"location" + +: [ + +52.519444 + +, +13.406667 + +] + + +} + + + +object_start + +key + +object_end + +array_start + +array_end + +value + diff --git a/docs/mkdocs/docs/images/json-dark.svg b/docs/mkdocs/docs/images/json-dark.svg new file mode 100644 index 000000000..5757e976e --- /dev/null +++ b/docs/mkdocs/docs/images/json-dark.svg @@ -0,0 +1 @@ +JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each) \ No newline at end of file diff --git a/docs/mkdocs/docs/images/json-light.svg b/docs/mkdocs/docs/images/json-light.svg new file mode 100644 index 000000000..97bd55bed --- /dev/null +++ b/docs/mkdocs/docs/images/json-light.svg @@ -0,0 +1 @@ +JSON for Modern C++: feature slideshow (animated, 20 slides, 15 s each) \ No newline at end of file diff --git a/docs/mkdocs/docs/images/json_syntax_number.png b/docs/mkdocs/docs/images/json_syntax_number.png deleted file mode 100644 index be23ffa69..000000000 Binary files a/docs/mkdocs/docs/images/json_syntax_number.png and /dev/null differ diff --git a/docs/mkdocs/docs/images/json_syntax_number.svg b/docs/mkdocs/docs/images/json_syntax_number.svg new file mode 100644 index 000000000..3a837a08c --- /dev/null +++ b/docs/mkdocs/docs/images/json_syntax_number.svg @@ -0,0 +1,66 @@ + +Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +− +0 +digit 1–9 +digit +. +digit +e +E +− ++ +digit +number +fraction +exponent + diff --git a/docs/mkdocs/docs/images/range-begin-end.svg b/docs/mkdocs/docs/images/range-begin-end.svg index 99c9f8662..61a4cc721 100644 --- a/docs/mkdocs/docs/images/range-begin-end.svg +++ b/docs/mkdocs/docs/images/range-begin-end.svg @@ -1,447 +1,28 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - Past-the-last element - - - - begin - - - - end - - - - + +A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position. + + + + + + + + + + + + + + + + + + +begin + + +end + +Past-the-last element + diff --git a/docs/mkdocs/docs/images/range-rbegin-rend.svg b/docs/mkdocs/docs/images/range-rbegin-rend.svg index 9667efd89..1e91f7158 100644 --- a/docs/mkdocs/docs/images/range-rbegin-rend.svg +++ b/docs/mkdocs/docs/images/range-rbegin-rend.svg @@ -1,1244 +1,54 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - Past-the-last element - - - - begin - - - - end - - - - - - - - - - - - - - - - - - - - - - - - - Reversed sequence - - Reverse past-the-last element - - - - - - - - rend - - - - rbegin - - - Reverse iterator stores an iterator to the nextelement than the one it actually refers to - - - - - - - - + +Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end(). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +rend + + +rbegin + +Reverse past-the-last element + + +A reverse iterator stores an iterator to the next element + + +begin + + +end + +Past-the-last element diff --git a/docs/mkdocs/docs/images/wordmark-header.svg b/docs/mkdocs/docs/images/wordmark-header.svg new file mode 100644 index 000000000..c69773e72 --- /dev/null +++ b/docs/mkdocs/docs/images/wordmark-header.svg @@ -0,0 +1 @@ +nlohmann/json \ No newline at end of file diff --git a/docs/mkdocs/docs/index.md b/docs/mkdocs/docs/index.md index f53b8dcab..51d3c97a6 100644 --- a/docs/mkdocs/docs/index.md +++ b/docs/mkdocs/docs/index.md @@ -1,63 +1,11 @@ +--- +template: home.html +hide: + - toc +--- + # JSON for Modern C++ -![JSON for Modern C++](images/json.gif) - -JSON for Modern C++ is a header-only C++11 library that turns JSON into a first-class C++ data type, using the operator -magic of modern C++ so that creating, reading, and modifying JSON values feels as natural as it does in languages like -Python. The whole library is available as a single header, `json.hpp`, with no dependencies, no subproject, and no -complex build system to set up; a companion header, `json_fwd.hpp`, provides forward declarations to keep compile times -down, `` holds the user-defined string literals on their own, and -`` adds zero-copy views and editable documents. See -[header-only integration](integration/index.md) for details. It is heavily unit-tested with 100% code -coverage, checked with Valgrind and the Clang Sanitizers for memory leaks, and continuously fuzz-tested by Google -OSS-Fuzz. - -## Quick start - -Add the single header to your project and use the library like this: - -```cpp -#include -#include - -using nlohmann::json; - -int main() -{ - // parse a JSON string - json j = json::parse(R"({"happy": true, "pi": 3.141})"); - - // access and modify values - j["name"] = "Niels"; - j["list"] = {1, 0, 2}; - - // read a value into a C++ variable and use it - double pi = j["pi"].get(); - std::cout << "2 * pi = " << 2 * pi << '\n'; - - // serialize with an indent of 4 spaces - std::cout << j.dump(4) << '\n'; -} -``` - -Get the library by copying the single header [`json.hpp`](https://github.com/nlohmann/json/releases) from the -releases page into a directory `nlohmann` on your include path, or by installing it with a package manager: - -```sh -brew install nlohmann-json # Homebrew -vcpkg install nlohmann-json # vcpkg -``` - -```cmake -find_package(nlohmann_json 3.12.0 REQUIRED) -target_link_libraries(myproject PRIVATE nlohmann_json::nlohmann_json) -``` - -See [Integration](integration/index.md) for CMake in detail, all supported package managers (Conan, Meson, Bazel, -Conda, and more), and pkg-config. - -## Explore the documentation -
- :octicons-rocket-24:{ .lg .middle } __Features__ @@ -112,11 +60,5 @@ Conda, and more), and pkg-config.
-!!! info "Unreleased changes" - - This documentation is built from the `develop` branch and may describe changes that are not part of a release - yet. Their version numbers are followed by an unreleased badge; see - [Releases](home/releases/index.md) for what shipped in each version. - -The library is licensed under the [MIT License](home/license.md). The source code, issue tracker, and discussions -are on [GitHub](https://github.com/nlohmann/json). +![JSON for Modern C++](images/json-light.svg#only-light) +![JSON for Modern C++](images/json-dark.svg#only-dark) diff --git a/docs/mkdocs/docs/integration/index.md b/docs/mkdocs/docs/integration/index.md index 6b461ff99..5993b0e23 100644 --- a/docs/mkdocs/docs/integration/index.md +++ b/docs/mkdocs/docs/integration/index.md @@ -5,13 +5,13 @@ pick one: ```mermaid flowchart TD - A[Add the library to a C++ project] --> B{Already using CMake?} - B -- no --> C{Using pkg-config or plain Makefiles?} + A[Add the library to a C++ project] --> B{{Already using CMake?}} + B -- no --> C{{Using pkg-config or plain Makefiles?}} C -- yes --> D[pkg-config] C -- no --> E[Copy the single header] - B -- yes --> F{Library installed system-wide?} + B -- yes --> F{{Library installed system-wide?}} F -- yes --> G["find_package()"] - F -- no --> H{Use a package manager?} + F -- no --> H{{Use a package manager?}} H -- yes --> I[Package manager] H -- no --> J["add_subdirectory() or FetchContent"] ``` diff --git a/docs/mkdocs/docs/js/mermaid-brand.js b/docs/mkdocs/docs/js/mermaid-brand.js new file mode 100644 index 000000000..2605db4c5 --- /dev/null +++ b/docs/mkdocs/docs/js/mermaid-brand.js @@ -0,0 +1,100 @@ +/* + * Brand look for Mermaid diagrams. + * + * Material for MkDocs loads Mermaid on demand and calls mermaid.initialize() with its own theme CSS. This script + * catches Mermaid when it arrives on window.mermaid and wraps two calls: + * - initialize(): adds the brand's font, spacing and shape rules below to Material's options; + * - render(): marks hexagons ({{...}} in a flowchart) with the class "nj-decision", since in the SVG a hexagon and + * a parallelogram are both a that CSS cannot tell apart; and keeps a wide diagram at 80 % of its size + * or more, so its text stays readable and the diagram scrolls sideways instead (see .mermaid in brand.css). + * The colours come from the --md-mermaid-* variables in brand.css, so the diagrams follow the light/dark toggle. + */ +(function () { + "use strict"; + + var css = [ + // text: Inter everywhere, labels on boxes semibold + ".label, .nodeLabel, .nodeLabel p, .edgeLabel, .edgeLabel p, .cluster span, text { font-family: var(--md-mermaid-font-family) !important; }", + ".nodeLabel, .nodeLabel p { font-weight: 600; }", + ".edgeLabel, .edgeLabel p { color: var(--md-default-fg-color--light) !important; font-size: 13px; font-weight: 500; }", + + // boxes: 1.5 px outline, 6 px corners; "(...)" nodes stay rounder than "[...]" nodes + ".node rect, .node path, .node polygon, .node circle, .node ellipse { stroke-width: 1.5px !important; }", + ".node rect:not([rx]) { rx: 6px; ry: 6px; }", + ".node rect[rx] { rx: 14px; ry: 14px; }", + + // decisions: solid blue hexagons with white text + ".node.nj-decision polygon { fill: var(--nj-band) !important; stroke: var(--nj-band) !important; stroke-linejoin: round; }", + ".node.nj-decision .nodeLabel, .node.nj-decision .nodeLabel p { color: #fff !important; }", + + // lines: 1.5 px, solid unless written as -.- + ".edgePath .path, .flowchart-link { stroke-width: 1.5px !important; }", + "marker path { stroke: none !important; }", + + // class diagrams; a node with its own fill ("style X fill:#CCCCFF" on the exception pages) is the highlighted + // one and gets soft blue, which also works in dark mode + "g.classGroup rect, .classGroup line, g.node.default > g > path { stroke-width: 1.5px !important; }", + ".node path[style*='fill'], .node rect[style*='fill'] { fill: var(--nj-blue-soft) !important; }", + ".relation { stroke-width: 1.5px !important; }", + + // sequence diagrams + ".actor { stroke-width: 1.5px !important; rx: 6px; ry: 6px; }", + ".actor-line { stroke-width: 1px; }", + ".messageLine0, .messageLine1 { stroke-width: 1.5px !important; }", + ".note { rx: 6px; ry: 6px; }" + ].join("\n"); + + var options = { + theme: "base", + fontFamily: "Inter, sans-serif", + themeVariables: { fontFamily: "Inter, sans-serif", fontSize: "15px" }, + flowchart: { curve: "basis", padding: 12, nodeSpacing: 36, rankSpacing: 48, wrappingWidth: 240 } + }; + + var minScale = 0.8; + + function postProcess(svg) { + // shrink to fit the column, but not below minScale + svg = svg.replace(/^(]*style="[^"]*)max-width:\s*([\d.]+)px;/, function (all, head, width) { + return head + "max-width: " + width + "px; min-width: " + Math.round(width * minScale) + "px;"; + }); + // add class "nj-decision" to every flowchart node drawn as a hexagon (a polygon with six corners) + return svg.replace(/]*)> +

// error 404

+

This page does not exist

+

The page may have moved or been renamed. Try the search in the header, or start from one of these pages.

+

+ Home + Features + API documentation +

+ +{% endblock %} diff --git a/docs/mkdocs/overrides/home.html b/docs/mkdocs/overrides/home.html new file mode 100644 index 000000000..5fb50dfbc --- /dev/null +++ b/docs/mkdocs/overrides/home.html @@ -0,0 +1,33 @@ +{#- Home page: the brand's blue hero band below the tabs (selected with "template: home.html" in index.md) -#} +{% extends "main.html" %} + +{% block tabs %} + {{ super() }} +
+
+
+

JSON for Modern C++

+

What if JSON was part of modern C++?

+

+ Get started + API documentation +

+
+ +
+
+{% endblock %} diff --git a/docs/mkdocs/scripts/figures/iterator_callback_figures.py b/docs/mkdocs/scripts/figures/iterator_callback_figures.py new file mode 100644 index 000000000..49d92ab28 --- /dev/null +++ b/docs/mkdocs/scripts/figures/iterator_callback_figures.py @@ -0,0 +1,103 @@ +"""Draws the parser-callback and iterator illustrations as an inline SVG into docs/images/. + +The SVG is not an image: the pages include its text with `--8<-- "images/callback_events.svg, range-begin-end.svg and range-rbegin-rend.svg"`, so it picks up the colours and fonts of the +page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/iterator_callback_figures.py` from docs/mkdocs after +changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the +abbreviation extension wraps it in an and the text disappears. +""" +import html +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +CW = 9.6 # JetBrains Mono advance at 16px + +def write(name, W, H, cls, tid, title, body, defs=""): + s = ['' % (cls, W, H, tid), + '%s' % (tid, html.escape(title).replace('J&#8203;SON', 'J​SON'))] + if defs: s.append(defs) + s += body + s.append('') + open(OUT / name, "w").write("\n".join(s) + "\n") + +# ---------- callback events ---------- +def callback(): + body, y0, lh = [], 74, 28 + body.append('J​SON to parse') + body.append('callback events') + lines = [ + (0, [("{", None), (None, "os")]), + (2, [('"name"', None), (None, "k"), (': "Berlin"', None), (None, "v"), (",", None)]), + (2, [('"location"', None), (None, "k"), (": [", None), (None, "as")]), + (4, [("52.519444", None), (None, "v"), (",", None)]), + (4, [("13.406667", None), (None, "v")]), + (2, [("]", None), (None, "ae"), (None, "v")]), + (0, [("}", None), (None, "oe"), (None, "v")]), + ] + for i, (indent, parts) in enumerate(lines): + y = y0 + i * lh + x = 16 + indent * CW + for text, ev in parts: + if text is not None: + body.append('%s' % (x, y, html.escape(text))) + x += CW * len(text) + else: + body.append('' % (ev, x + 10, y - 5)) + x += 20 if text is None else 0 + legend = [("os", "object_start"), ("k", "key"), ("oe", "object_end"), ("as", "array_start"), ("ae", "array_end"), ("v", "value")] + for i, (ev, label) in enumerate(legend): + y = 74 + i * 30 + body.append('' % (ev, y - 5)) + body.append('%s' % (y, label)) + write("callback_events.svg", 480, 262, "nj-fig--cb", "fig-cb-title", + "Example of the parse events a callback sees for the J​SON text {\"name\": \"Berlin\", \"location\": [52.519444, 13.406667]}: object_start after the opening brace, key after each key, array_start after the opening bracket, value after each value, array_end after the closing bracket, and object_end after the closing brace. Every array and object also produces a value event when it is complete.", body) + +# ---------- ranges ---------- +BW, GAP, X0 = 30, 4, 30 +def cell_x(i): return X0 + i * (BW + GAP) +def cell(i, y, kind): + c = "fg-cell" + (" fg-cell--end" if kind == "end" else "") + return '' % (c, cell_x(i), y, BW) +def pill(cx, y, label, W, label_w): + x = max(4, min(W - 4 - label_w, cx - label_w / 2)) + return ['' % (x, y, label_w), + '%s' % (x + label_w / 2, y + 16.5, label)] +def arrow(x, ya, yb, mid, dashed=False): + return '' % (" fg-arrow--dash" if dashed else "", x, ya, x, yb, mid) + +def ranges1(): + W, H, mid = 600, 150, "fig-be-arrow" + defs = '' % mid + body = [] + for i in range(15): body.append(cell(i, 62, "elem")) + body.append(cell(15, 62, "end")) + body += pill(cell_x(0) + BW / 2, 8, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) + body += pill(cell_x(15) + BW / 2, 8, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) + body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 18)) + body.append(arrow(cell_x(15) + BW / 2, 124, 96, mid)) + write("range-begin-end.svg", W, H, "nj-fig--range", "fig-be-title", + "A row of fifteen elements followed by a dashed past-the-last position. begin() points to the first element and end() points to the past-the-last position.", body, defs) + +def ranges2(): + W, H, mid = 600, 236, "fig-rbe-arrow" + defs = '' % mid + body = [] + ytop, ybot = 62, 144 + body.append(cell(0, ytop, "end")) + for i in range(1, 16): body.append(cell(i, ytop, "elem")) + for i in range(15): body.append(cell(i, ybot, "elem")) + body.append(cell(15, ybot, "end")) + body += pill(cell_x(0) + BW / 2, 8, "rend", W, 52); body.append(arrow(cell_x(0) + BW / 2, 32, 60, mid)) + body += pill(cell_x(15) + BW / 2, 8, "rbegin", W, 68); body.append(arrow(cell_x(15) + BW / 2, 32, 60, mid)) + body.append('Reverse past-the-last element' % (cell_x(0) + BW / 2 + 36)) + for col in (0, 15): + x = cell_x(col) + BW / 2 + body.append('' % (x, x, ybot - 2)) + body.append('A reverse iterator stores an iterator to the next element') + body.append('') + body += pill(cell_x(0) + BW / 2, 204, "begin", W, 60); body.append(arrow(cell_x(0) + BW / 2, 202, 178, mid)) + body += pill(cell_x(15) + BW / 2, 204, "end", W, 48); body.append(arrow(cell_x(15) + BW / 2, 202, 178, mid)) + body.append('Past-the-last element' % (cell_x(15) + BW / 2 - 40)) + write("range-rbegin-rend.svg", W, H, "nj-fig--range", "fig-rbe-title", + "Two rows of sixteen cells. The upper row shows the reversed sequence: rend() points to a dashed position before the first element and rbegin() to the last element. The lower row shows the forward sequence: begin() points to the first element and end() to a dashed position after the last element. A reverse iterator stores an iterator to the next element, so rend() corresponds to begin() and rbegin() to end().", body, defs) + +callback(); ranges1(); ranges2() diff --git a/docs/mkdocs/scripts/figures/railroad_number.py b/docs/mkdocs/scripts/figures/railroad_number.py new file mode 100644 index 000000000..033bab87b --- /dev/null +++ b/docs/mkdocs/scripts/figures/railroad_number.py @@ -0,0 +1,76 @@ +"""Draws the JSON number railroad diagram as an inline SVG into docs/images/. + +The SVG is not an image: the pages include its text with `--8<-- "images/json_syntax_number.svg"`, so it picks up the colours and fonts of the +page (see the `railroad` and `nj-fig` rules in docs/css/brand.css). Run `python3 scripts/figures/railroad_number.py` from docs/mkdocs after +changing the drawing, and commit the generated file. In SVG text write the word JSON as `J​SON`, otherwise the +abbreviation extension wraps it in an and the text disappears. +""" +import math +from pathlib import Path + +OUT = Path(__file__).resolve().parents[2] / "docs" / "images" +Y0, Y1, Y2, Y3 = 44, 92, 136, 180 +R = 10 +paths, shapes, texts = [], [], [] + +def rounded(pts, r=R): + d = "M%g %g" % pts[0] + for i in range(1, len(pts) - 1): + (x0, y0), (x1, y1), (x2, y2) = pts[i-1], pts[i], pts[i+1] + l1 = math.hypot(x1-x0, y1-y0); l2 = math.hypot(x2-x1, y2-y1) + rr = min(r, l1/2, l2/2) + ax, ay = x1 - (x1-x0)/l1*rr, y1 - (y1-y0)/l1*rr + bx, by = x1 + (x2-x1)/l2*rr, y1 + (y2-y1)/l2*rr + d += " L%g %g Q%g %g %g %g" % (ax, ay, x1, y1, bx, by) + d += " L%g %g" % pts[-1] + return d + +def path(*pts): paths.append(rounded(list(pts))) +def circle(cx, cy, ch): + shapes.append('' % (cx, cy)) + shapes.append('' % (cx, cy)) + texts.append('%s' % (cx, cy+5, ch)) +def box(x0, x1, cy, label): + shapes.append('' % (x0, cy-14, x1-x0)) + shapes.append('' % (x0, cy-14, x1-x0)) + texts.append('%s' % ((x0+x1)/2, cy+5, label)) +def loop(xr, xl, cy): # return path below a box row + path((xr, cy), (xr, cy+34), (xl, cy+34), (xl, cy)) + +# start / main +path((8, Y0), (850, Y0)) +# A: optional minus +path((24, Y0), (38, Y0), (38, Y1), (59, Y1)); circle(72, Y1, "−"); path((85, Y1), (106, Y1), (106, Y0), (120, Y0)) +# B: 0 | 1-9 digit* +circle(257, Y0, "0") +path((120, Y0), (134, Y0), (134, Y1), (160, Y1)); box(160, 242, Y1, "digit 1–9") +path((242, Y1), (380, Y1), (380, Y0), (396, Y0)) +path((242, Y1), (258, Y1), (258, Y2), (278, Y2)); box(278, 332, Y2, "digit") +path((332, Y2), (348, Y2), (348, Y1)); loop(340, 270, Y2) +# C: fraction +path((396, Y0), (410, Y0), (410, Y1), (425, Y1)); circle(438, Y1, ".") +path((451, Y1), (462, Y1)); box(462, 516, Y1, "digit"); loop(524, 456, Y1) +path((516, Y1), (530, Y1), (530, Y0), (546, Y0)) +# D: exponent +path((546, Y0), (560, Y0), (560, Y1), (577, Y1)); circle(590, Y1, "e") +path((546, Y0), (560, Y0), (560, Y2), (577, Y2)); circle(590, Y2, "E") +path((603, Y1), (728, Y1)); path((603, Y2), (620, Y2), (620, Y1)) +path((640, Y1), (654, Y1), (654, Y2), (670, Y2)); circle(683, Y2, "−") +path((640, Y1), (654, Y1), (654, Y3), (670, Y3)); circle(683, Y3, "+") +path((696, Y2), (712, Y2), (712, Y1)); path((696, Y3), (712, Y3), (712, Y1)) +path((728, Y1), (746, Y1)); box(746, 800, Y1, "digit"); loop(808, 738, Y1) +path((800, Y1), (816, Y1), (816, Y0), (832, Y0)) + +svg = ['', + 'Railroad diagram of the J​SON number syntax: optional minus, then 0 or a digit from 1 to 9 followed by digits, then an optional fraction (a dot and digits) and an optional exponent (e or E, an optional sign, and digits).', + ''] +for d in paths: svg.append('' % d) +svg.append('') +svg.append('' % (Y0-6, Y0, Y0+6)) +svg.append('' % (Y0-6, Y0, Y0+6)) +svg += shapes + texts +svg.append('number' % (Y0-12)) +svg.append('fraction' % (Y0-12)) +svg.append('exponent' % (Y0-12)) +svg.append('') +open(OUT / "json_syntax_number.svg", "w").write("\n".join(svg) + "\n") diff --git a/docs/mkdocs/scripts/slideshow/.gitignore b/docs/mkdocs/scripts/slideshow/.gitignore new file mode 100644 index 000000000..0480ebe75 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/.gitignore @@ -0,0 +1 @@ +fonts/ diff --git a/docs/mkdocs/scripts/slideshow/Makefile b/docs/mkdocs/scripts/slideshow/Makefile new file mode 100644 index 000000000..ff343ef4e --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/Makefile @@ -0,0 +1,73 @@ +# Rebuild the animated feature slideshow of the home page (docs/images/json-light.svg and json-dark.svg) when +# slides.md, build.py or the fonts change. Needs uv (build.py declares its dependencies inline, PEP 723), curl, +# unzip and shasum. Run it from docs/mkdocs/scripts/slideshow. +# +# make download missing fonts and build both SVGs +# make fonts download only the fonts +# make SECONDS=12 change the time per slide +# make clean remove the generated SVGs +# make distclean also remove the downloaded fonts + +SECONDS ?= 15 + +IMAGES := ../../docs/images +LIGHT := $(IMAGES)/json-light.svg +DARK := $(IMAGES)/json-dark.svg + +RUN := uv run --script build.py +FONTS := fonts/Inter.ttf fonts/JetBrainsMono.ttf +DEPS := build.py slides.md $(FONTS) + +# Pinned sources; the checksums make a changed upstream file fail instead of silently changing the output. +# Inter: Google Fonts repository at a fixed commit (variable font with opsz and wght axes). +INTER_URL := https://raw.githubusercontent.com/google/fonts/0b58fb370093f9a9f4ff785d94405710b79de67c/ofl/inter/Inter%5Bopsz%2Cwght%5D.ttf +# JetBrains Mono: release 2.304 (the variable font in fonts/variable/ of the zip). +JBM_URL := https://github.com/JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip +JBM_ZIP := fonts/JetBrainsMono-2.304.zip + +SHA256_Inter.ttf := 29160a80ff49ddcab2c97711247e08b1fab27a484a329ce8b813d820dc559031 +SHA256_JetBrainsMono.ttf := 662a196d58f1183bf2d77428b6d5283fe3f45161ab021bea4036bc98e5cac016 +SHA256_JetBrainsMono-2.304.zip := 6f6376c6ed2960ea8a963cd7387ec9d76e3f629125bc33d1fdcd7eb7012f7bbf + +# Check $@.tmp against its pinned checksum, then move it into place. +define verify + echo '$(SHA256_$(notdir $@)) $@.tmp' | shasum -a 256 -c --quiet - || { rm -f $@.tmp; exit 1; } + mv $@.tmp $@ +endef + +.PHONY: all fonts clean distclean +.INTERMEDIATE: $(JBM_ZIP) + +all: $(LIGHT) $(DARK) + +fonts: $(FONTS) + +# fixed colours for the docs: an cannot see Material's theme toggle, so the home page includes both +# and shows the one that matches +$(LIGHT): $(DEPS) + $(RUN) $@.tmp $(SECONDS) light + mv $@.tmp $@ + +$(DARK): $(DEPS) + $(RUN) $@.tmp $(SECONDS) dark + mv $@.tmp $@ + +fonts/Inter.ttf: + mkdir -p fonts + curl -fsSL -m 300 -o $@.tmp '$(INTER_URL)' + $(verify) + +$(JBM_ZIP): + mkdir -p fonts + curl -fsSL -m 300 -o $@.tmp '$(JBM_URL)' + $(verify) + +fonts/JetBrainsMono.ttf: | $(JBM_ZIP) + unzip -p $(JBM_ZIP) 'fonts/variable/JetBrainsMono[[]wght].ttf' > $@.tmp + $(verify) + +clean: + rm -f $(LIGHT) $(DARK) $(IMAGES)/*.svg.tmp + +distclean: clean + rm -f $(FONTS) fonts/*.tmp $(JBM_ZIP) diff --git a/docs/mkdocs/scripts/slideshow/README.md b/docs/mkdocs/scripts/slideshow/README.md new file mode 100644 index 000000000..cd3ede5c8 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/README.md @@ -0,0 +1,27 @@ +# Feature slideshow of the home page + +`make` builds `docs/images/json-light.svg` and `json-dark.svg`, the animated feature tour on the home page. They replace +`json.gif` there. All text is outlined, so the SVGs need no fonts. Material's theme toggle cannot be seen from an +``, so the home page includes both files (`#only-light`, `#only-dark`) and Material shows the one that matches. + +The slides are in [`slides.md`](slides.md): one `# Title` per slide (`{badge: TEXT}` at the end adds a red chip), `- ` +bullets, and one fenced code block. `build.py` adds the title and closing slides around them, so the slide count follows +the file. Every snippet should compile against the current `develop`. Use a non-breaking space to keep words together +("16 bytes"). + +```bash +make # download missing fonts, build both SVGs +make SECONDS=12 +make clean # remove the SVGs; make distclean also removes the fonts +``` + +Run it from this directory. It needs [uv](https://docs.astral.sh/uv/), plus curl, unzip and shasum for the fonts: +`build.py` declares its dependencies inline (PEP 723, pinned, so the output stays byte-identical) and `uv run --script` +provides them. `fonts/` is git-ignored: Inter (variable, Google Fonts repository) and JetBrains Mono 2.304 (variable) +are downloaded from pinned URLs and checked against their SHA-256; a changed upstream file stops the build. +`./build.py OUT.svg [seconds] [auto|light|dark]` works directly too. A mistake in `slides.md` stops the build with +`slides.md:LINE` and leaves the old SVGs in place. + +Layout: title 48 px, bullets 24 px, code 19 px; the build warns when a slide's code would drop below 18 px. Syntax colours +are the `code-*` tokens of the brand (also used by `css/brand.css`). A CSS animation dissolves each slide over the +previous one and fills the progress bar; with reduced motion only the first slide shows. diff --git a/docs/mkdocs/scripts/slideshow/build.py b/docs/mkdocs/scripts/slideshow/build.py new file mode 100755 index 000000000..1bd45197a --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/build.py @@ -0,0 +1,461 @@ +#!/usr/bin/env -S uv run --script +# /// script +# requires-python = ">=3.9" +# dependencies = ["fonttools==4.67.0", "uharfbuzz==0.57.0"] +# /// +"""Animated SVG slideshow prototype for nlohmann/json, all text outlined (harfbuzz shaping, glyphs as + ). + +Usage: uv run --script build.py OUT.svg [seconds-per-slide] [auto|light|dark] + +auto (the default) follows prefers-color-scheme. light and dark fix the colours, for pages with their own theme +toggle (MkDocs Material), which an cannot see. + +The pinned versions keep the output byte-identical; a different harfbuzz may shape slightly differently. +""" +import re, sys, os +import uharfbuzz as hb +from fontTools.pens.svgPathPen import SVGPathPen + +HERE = os.path.dirname(os.path.abspath(__file__)) +FONTS = os.path.join(HERE, "fonts") +W, H = 1200, 675 +TOTAL = 0 # number of slides, set once slides.md is read (for the "02 / 20" labels and the progress segments) + +# ---------------------------------------------------------------- glyphs + +class Face: + def __init__(self, file, key, feats=None): + blob = hb.Blob.from_file_path(os.path.join(FONTS, file)) + self.face = hb.Face(blob) + self.upem = self.face.upem + self.key = key + self.feats = feats or {} + + def font(self, var): + f = hb.Font(self.face) + f.set_variations(var) + return f + + +INTER = Face("Inter.ttf", "i", {"cv11": True, "ss03": True, "kern": True}) +MONO = Face("JetBrainsMono.ttf", "m", {"calt": False, "liga": False}) + +GLYPHS = {} # (face, var, gid) -> id +DEFS = [] + + +def gid_ref(face, font, var, gid): + k = (face.key, tuple(sorted(var.items())), gid) + if k not in GLYPHS: + pen = SVGPathPen(None, ntos=lambda v: str(round(v))) + font.draw_glyph_with_pen(gid, pen) + d = pen.getCommands() + gidname = f"g{len(GLYPHS):x}" + GLYPHS[k] = gidname if d else None + if d: + DEFS.append(f'') + return GLYPHS[k] + + +def shape(face, text, wght, size, track=0.0): + if not text: + return [], 0, face.upem + var = {"wght": wght} + if face is INTER: + var["opsz"] = max(14, min(32, size)) + font = face.font(var) + buf = hb.Buffer() + buf.add_str(text) + buf.guess_segment_properties() + hb.shape(font, buf, face.feats) + out, x = [], 0 + tr = track * face.upem + for info, pos in zip(buf.glyph_infos, buf.glyph_positions): + out.append((gid_ref(face, font, var, info.codepoint), x + pos.x_offset, pos.y_offset)) + x += pos.x_advance + tr + return out, x - tr, face.upem + + +def width(text, face=INTER, wght=400, size=16, track=0.0): + _, w, upem = shape(face, text, wght, size, track) + return w * size / upem + + +def run(text, x, y, size, wght=400, cls="tx", face=INTER, track=0.0): + """One line of text, baseline at y. Returns (svg, width_px).""" + glyphs, w, upem = shape(face, text, wght, size, track) + s = size / upem + uses = "".join(f'" + for g, gx, gy in glyphs if g) + return (f'{uses}', w * s) + + +def runs(parts, x, y, size, face=INTER, track=0.0): + """Several (text, wght, cls) pieces on one line.""" + out = [] + for text, wght, cls in parts: + svg, w = run(text, x, y, size, wght, cls, face, track) + out.append(svg) + x += w + track * size + return "".join(out), x + + +def wrap(text, maxw, size, wght=400): + lines = _wrap(text, maxw, size, wght) + # no one-word widow: pull the last word of the previous line down if it still fits + if len(lines) > 1 and " " not in lines[-1] and " " in lines[-2]: + head, last = lines[-2].rsplit(" ", 1) + if width(last + " " + lines[-1], size=size, wght=wght) <= maxw: + lines[-2:] = [head, last + " " + lines[-1]] + return lines + + +def _wrap(text, maxw, size, wght=400): + lines, cur = [], "" + for word in text.split(" "): + t = (cur + " " + word).strip() + if cur and width(t, size=size, wght=wght) > maxw: + lines.append(cur) + cur = word + else: + cur = t + return lines + [cur] + +# ---------------------------------------------------------------- pieces + + +def wordmark(x, y, size, on="page"): + """The wordmark with hand kerning around the slash (see the logo in the brand kit). Below 32 px it switches to the + small optical master (nlohmann 250 / slash 450), because Thin falls apart at footer size.""" + a, sl = ("wm-a", "wm-s") if on == "page" else ("on-a", "on-s") + small = size < 32 + w = (250, 450, 800) if small else (100, 400, 800) + k = (0.004, 0.024) if small else (-0.006, 0.012) + out = [] + for i, (t, wg, c) in enumerate([("nlohmann", w[0], a), ("/", w[1], sl), ("json", w[2], a)]): + svg, wd = run(t, x, y, size, wg, c, track=-0.02) + out.append(svg) + x += wd - 0.02 * size + (k[i] * size if i < 2 else 0) + return "".join(out), x + + +KW = {"using", "auto", "return", "const", "struct", "for", "override"} +TYPES = {"int", "void", "bool", "double", "char", "float"} +CONSTANTS = {"true", "false", "nullptr"} +TOKEN = re.compile(r'(//.*$)|(R"\(.*?\)"|R"\(.*$|"(?:[^"\\]|\\.)*"|\'(?:[^\'\\]|\\.)\')|(<[\w/.]+>(?=\s*$))|(#\w+)' + r'|(\b\d+(?:\.\d+)?\b)|(\b[A-Za-z_]\w*\b)|(\s+)|(.)') + + +def code_block(lines, x, y, size, lh): + """Lines of C++; a raw string literal may span lines.""" + out, in_raw = [], False + for line in lines: + if line: + svg, in_raw = code_line(line, x, y, size, in_raw) + out.append(svg) + y += lh + else: + y += lh * BLANK + return "".join(out) + + +BLANK = 0.55 # an empty code line is a paragraph break, not a full line + + +def code_height(lines, size, lh): + n_full = sum(1 for l in lines if l) + n_blank = len(lines) - n_full + return lh * (n_full - 1) + lh * BLANK * n_blank + size + + +def code_line(line, x, y, size, in_raw=False): + if line.startswith("$ "): + return runs([("$ ", 400, "tx3"), (line[2:], 400, "tx")], x, y, size, MONO)[0], False + out, cx = [], x + if in_raw: + end = line.find(')"') + head = line if end < 0 else line[:end + 2] + svg, w = run(head, cx, y, size, 400, "str", MONO) + out.append(svg) + if end < 0: + return "".join(out), True + cx += w + line = line[end + 2:] + in_raw = False + for m in TOKEN.finditer(line): + com, st, inc, pp, num, ident, ws, other = m.groups() + t = m.group(0) + if st and st.startswith('R"(') and not st.endswith(')"'): + in_raw = True + face, wght, cls = MONO, 400, "tx" + if com: + cls = "com" # upright: JetBrains Mono Italic's cursive f reads as a florin at README size + elif st or inc: + cls = "str" + elif pp: + cls = "pp" + elif ident in KW: + wght, cls = 600, "kw" + elif ident in TYPES: + cls = "ty" + elif num or ident in CONSTANTS: + cls = "num" + elif ident and line[m.end():m.end() + 1] == "(": + cls = "fn" # a call or definition: name directly followed by "(" + svg, w = run(t, cx, y, size, wght, cls, face) + if not ws: + out.append(svg) + cx += w + return "".join(out), in_raw + + +SEG, SEG_GAP = 20, 5 + + +def progress(idx, n_proto, y): + """TOTAL segments at the bottom right; earlier slides full, current one fills over the slide's time.""" + seg, gap = SEG, SEG_GAP + x0 = W - 64 - TOTAL * seg - (TOTAL - 1) * gap + out = [] + for k in range(TOTAL): + x = x0 + k * (seg + gap) + out.append(f'') + if k < idx: + out.append(f'') + elif k == idx: + out.append(f'') + return "".join(out) + + +def pattern_def(): + tile = [("{", 14, 34), ("]", 92, 22), (":", 136, 60), ("[", 52, 82), (",", 118, 112), + ("}", 20, 140), (":", 78, 150), ("{", 140, 158)] + glyphs = "".join(run(c, x, y, 26, 600, "pat", MONO)[0] for c, x, y in tile) + return f'{glyphs}' + +# ---------------------------------------------------------------- slides + + +CODE_SIZES = [] +SLOGAN = "What if JSON was part of modern C++?" + + +def band_bg(): + """Hero gradient plus the syntax pattern, faded out towards the text side (left).""" + return ('' + '') + + +def footer_band(idx, n, left=None): + out = [] + if left: + out.append(run(left, 64, FOOT_Y, 20, 600, "on-tx")[0]) + out.append(progress(idx, n, FOOT_Y - 7.5).replace('class="seg"', 'class="seg on-band"').replace('class="seg-on', 'class="seg-on on-band')) + return "".join(out) + + +FOOT_Y = 640 # one footer baseline on every slide: wordmark or URL left, progress right + + +def slide_title(idx, n): + out = [band_bg()] + x = 96 + label = "VERSION 3.13.0" + lw = width(label, MONO, 500, 15, 0.04) + out.append(f'') + out.append(run(label, x + 14, 208.5, 15, 500, "on-tx", MONO, 0.04)[0]) + out.append(wordmark(x - 6, 336, 112, on="band")[0]) + out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 404, 26, MONO)[0]) + out.append(footer_band(idx, n, "github.com/nlohmann/json")) + return "".join(out) + + +def slide_feature(idx, n, title, bullets, code, badge=None): + out = [''] + x, top, bottom = 64, 136, 596 + # title row: 48/800, -3 % + svg, tw = run(title, x - 2, 98, 48, 800, "tx", track=-0.03) + out.append(svg) + if badge: + cx = x + tw + 16 + cw = width(badge, MONO, 600, 13, 0.06) + 18 + out.append(f'') + out.append(run(badge, cx + 9, 86.2, 13, 600, "on-tx", MONO, 0.06)[0]) + # code panel: fixed left edge, fixed width, fixed top on every slide; only its height follows the code + pad, lf = 28, 1.45 + px, pw = 488, 1136 - 488 + em = max(width(l, MONO, 400, 1) for l in code) + cs = 19.0 + while cs > 14 and (em * cs > pw - 2 * pad or code_height(code, cs, lf * cs) > bottom - top - 2 * pad): + cs -= 0.5 + CODE_SIZES.append((title, cs)) + if cs < 18: + print(f" warning: {title!r} code at {cs} px ({em * 19:.0f} px wide at 19 px)", file=sys.stderr) + lh = lf * cs + ph = max(2 * pad + code_height(code, cs, lh), 240) + out.append(f'') + first = top + pad + cs * 0.78 + out.append(code_block(code, px + pad, first, cs, lh)) + # bullets: 24/400 on 34, first baseline level with the first code baseline + colw = px - x - 40 + by = first + for b in bullets: + out.append(f'') + for line in wrap(b, colw - 24, 24): + out.append(run(line, x + 24, by, 24, 400, "tx2")[0]) + by += 34 + by += 16 + # footer: small-master wordmark left, progress right, no rule, no numeral + out.append(wordmark(64, FOOT_Y, 22)[0]) + out.append(progress(idx, n, FOOT_Y - 7.5)) + return "".join(out) + + +def slide_closing(idx, n): + out = [band_bg()] + x = 96 + out.append(wordmark(x - 4, 146, 80, on="band")[0]) + out.append(runs([("// ", 400, "w70"), (SLOGAN, 400, "on-s")], x, 200, 22, MONO)[0]) + info = [ + ("{", None), + (' "copyright": ', "(c) 2013-2026 Niels Lohmann"), + (' "license": ', "MIT"), + (' "docs": ', "json.nlohmann.me"), + (' "github": ', "github.com/nlohmann/json"), + (' "sponsor": ', "github.com/sponsors/nlohmann"), + (' "mail": ', "mail@nlohmann.me"), + ("}", None), + ] + cs, lh, pad = 21, 33, 28 + py = 244 + pw = max(width(k + '"' + (v or "") + '",', MONO, 400, cs) for k, v in info) + 2 * pad + out.append(f'') + y = py + pad + cs * 0.8 + for k, (key, val) in enumerate(info): + if val is None: + out.append(run(key, x + pad, y, cs, 400, "on-tx", MONO)[0]) + else: + comma = "," if k < len(info) - 2 else "" + out.append(runs([(key, 400, "on-tx"), (f'"{val}"', 400, "on-s"), (comma, 400, "on-tx")], x + pad, y, cs, MONO)[0]) + y += lh + out.append(footer_band(idx, n)) + return "".join(out) + + +def load_slides(path): + """Feature slides from Markdown: '# Title {badge: TEXT}', '- bullet' lines, one fenced code block.""" + text = re.sub(r"", "", open(path).read(), flags=re.S) + slides, cur, in_code = [], None, False + for no, line in enumerate(text.split("\n"), 1): + if in_code: + if line.startswith("```"): + in_code = False + else: + cur["code"].append(line) + elif line.startswith("```"): + in_code = True + elif line.startswith("# "): + title, badge = line[2:].strip(), None + m = re.search(r"\s*\{badge:\s*(.*?)\}$", title) + if m: + title, badge = title[:m.start()], m.group(1) + cur = {"title": title, "bullets": [], "code": [], "badge": badge} + slides.append(cur) + elif line.startswith("- ") and cur: + cur["bullets"].append(line[2:].strip()) + elif line[:1] in " \t" and line.strip() and cur and cur["bullets"]: + cur["bullets"][-1] += " " + line.strip() + elif line.strip(): + raise SystemExit(f"{path}:{no}: unexpected line: {line!r}") + for sl in slides: + while sl["code"] and not sl["code"][-1].strip(): + sl["code"].pop() + if not sl["code"]: + raise SystemExit(f"{path}: slide {sl['title']!r} has no code block") + return slides + + +FEATURES = load_slides(os.path.join(HERE, "slides.md")) + + +def _feature(f): + return lambda i, n: slide_feature(i, n, f["title"], f["bullets"], f["code"], f["badge"]) + + +SLIDES = [slide_title] + [_feature(f) for f in FEATURES] + [slide_closing] +TOTAL = len(SLIDES) + + +# ---------------------------------------------------------------- assemble + + +def build(sec): + n = len(SLIDES) + T = n * sec + fade = 0.6 + a, b, c = fade / T * 100, sec / T * 100, (sec + fade) / T * 100 + def cls(i): + return "s s0" if i == 0 else "s sl" if i == n - 1 else "s" + bodies = [f'{fn(i, n)}' for i, fn in enumerate(SLIDES)] + css = f""" +.bg{{fill:#fff}} .tx{{fill:#0f1419}} .tx2{{fill:#3d444f}} .tx3{{fill:#8a919c}} .com{{fill:#646d7a}} +.bl{{fill:#1747a6}} .blk{{fill:#1747a6}} .kw{{fill:#1747a6}} .ty{{fill:#0d6e72}} .fn{{fill:#6b3fb8}} .str{{fill:#23793a}} .num{{fill:#b04a07}} .pp{{fill:#a3247a}} +.panel{{fill:#f5f7fa;stroke:#e3e6eb;stroke-width:1}} .rule{{stroke:#e3e6eb;stroke-width:1}} +.wm-a{{fill:#0f1419}} .wm-s{{fill:#1747a6}} +.on-a,.on-tx{{fill:#fff}} .red{{fill:#d93a3f}} .on-panel{{fill:#fff;fill-opacity:.08;stroke:#fff;stroke-opacity:.16}} .on-s{{fill:#bcd3fb}} .w70{{fill:#fff;fill-opacity:.7}} .on-tx2{{fill:#dbe6fb}} .chip{{fill:#fff;fill-opacity:.14}} +.pat{{fill:#fff;fill-opacity:.09}} +.h1{{stop-color:#1d55c4}} .h2{{stop-color:#123a8a}} +.seg{{fill:#e3e6eb}} .seg-on{{fill:#1747a6}} +.seg.on-band{{fill:#fff;fill-opacity:.25}} .seg-on.on-band{{fill:#fff;fill-opacity:1}} +@media (prefers-color-scheme:dark){{ + .bg{{fill:#0f1218}} .tx,.wm-a{{fill:#f2f4f7}} .tx2{{fill:#c3c8d1}} .tx3{{fill:#7b828e}} .com{{fill:#8b929e}} + .bl,.blk,.wm-s,.seg-on,.kw{{fill:#7aa7f5}} .ty{{fill:#4fd1c5}} .fn{{fill:#c4a3ff}} .str{{fill:#7ed491}} .num{{fill:#f5a65b}} .pp{{fill:#f48fc8}} + .panel{{fill:#171b23;stroke:#272c36}} .rule{{stroke:#272c36}} .seg{{fill:#272c36}} + .red{{fill:#c8383d}} .h1{{stop-color:#163f93}} .h2{{stop-color:#0c2556}} +}} +.s{{opacity:0;animation:show {T:g}s linear infinite backwards}} +.fill{{transform-box:fill-box;transform-origin:left;animation:fill {T:g}s linear infinite backwards;animation-delay:inherit}} +.s0{{animation-name:show0}} .sl{{animation-name:showl}} +@keyframes show{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes show0{{0%{{opacity:1}} {c:.4f}%{{opacity:1}} {c + .01:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes showl{{0%{{opacity:0;animation-timing-function:cubic-bezier(.2,.6,.2,1)}} {a:.4f}%{{opacity:1}} {b:.4f}%{{opacity:1}} {c:.4f}%{{opacity:0}} 100%{{opacity:0}}}} +@keyframes fill{{0%{{transform:scaleX(0)}} {b:.4f}%{{transform:scaleX(1)}} 100%{{transform:scaleX(1)}}}} +@media (prefers-reduced-motion:reduce){{.s,.fill{{animation:none}} .s:first-of-type{{opacity:1}}}} +""" + css = re.sub(r"\n\s*", "", css) + pat = pattern_def() + return (f'' + f'JSON for Modern C++: feature slideshow (animated, {n} slides, {sec:g} s each)' + f'' + f'{"".join(DEFS)}{pat}' + f'' + f'' + f'' + f'' + f'{"".join(bodies)}') + + +def fix_scheme(svg, scheme): + """Drop the dark-mode block (light) or apply it unconditionally (dark).""" + if scheme == "auto": + return svg + start = svg.index("@media (prefers-color-scheme:dark){") + i, depth = svg.index("{", start), 0 + for j in range(i, len(svg)): + depth += {"{": 1, "}": -1}.get(svg[j], 0) + if depth == 0: + break + inner = svg[i + 1:j] if scheme == "dark" else "" + return svg[:start] + inner + svg[j + 1:] + + +if __name__ == "__main__": + out = sys.argv[1] + sec = float(sys.argv[2]) if len(sys.argv) > 2 else 15 + scheme = sys.argv[3] if len(sys.argv) > 3 else "auto" + if scheme not in ("auto", "light", "dark"): + sys.exit(f"unknown colour scheme {scheme!r}: use auto, light or dark") + svg = fix_scheme(build(sec), scheme) + # defs are appended while slides render, so build the slides first (done inside build via f-string order) + open(out, "w").write(svg) + print(out, len(svg.encode()) // 1024, "KB,", len(GLYPHS), "glyphs; code px:", " ".join(f"{c:g}" for _, c in CODE_SIZES)) diff --git a/docs/mkdocs/scripts/slideshow/slides.md b/docs/mkdocs/scripts/slideshow/slides.md new file mode 100644 index 000000000..fcfe55d75 --- /dev/null +++ b/docs/mkdocs/scripts/slideshow/slides.md @@ -0,0 +1,412 @@ + + +# Trivial integration + +- single header file +- no build system needed +- vanilla C++11 +- works with GCC 4.8+, Clang 3.4+ and MSVC 2015+ + +```cpp +#include + +// for convenience +using json = nlohmann::json; + +int main() +{ + // a JSON pretty-printer in 3 lines + json j; + std::cin >> j; + std::cout << std::setw(4) << j << std::endl; +} + +$ g++ pretty.cpp -std=c++11 -o pretty +``` + +# First-class datatype + +- write JSON like any other C++ literal +- initializer lists become arrays and objects + +```cpp +json j = { + {"pi", 3.141}, + {"happy", true}, + {"name", "Niels"}, + {"nothing", nullptr}, + {"answer", { + {"everything", 42} + }}, + {"list", {1, 0, 2}}, + {"object", { + {"currency", "USD"}, + {"value", 42.99} + }} +}; +``` + +# Array construction + +- arrays work the way you expect +- adding to a null value makes it an array + +```cpp +// create null value +json j; + +// add via member function +j.push_back("Hello"); +j.emplace_back("World"); + +// add via += operator +j += false; + +// add via index +j[2] = 42.3; + +// gaps are filled with null +j[4] = "World"; +``` + +# Object construction + +- objects work the way you expect +- adding to a null value makes it an object + +```cpp +// create null value +json j; + +// add via member function +j.emplace("key2", "value"); +j.push_back({"key", "value"}); + +// add via += operator +j += {"another key", false}; + +// add via key +j["pi"] = 3.1415926; +j["answer"]["everything"] = 42; +``` + +# Container operations + +- use JSON like an STL container +- satisfies the ReversibleContainer requirements + +```cpp +// container empty? +j.empty(); + +// current number of elements +j.size(); + +// maximal number of elements +j.max_size(); + +// swap elements +j.swap(j2); +std::swap(j, j2); + +// clear a container +j.clear(); +``` + +# STL containers + +- switch between STL and JSON containers +- conversion works in both directions + +```cpp +// some STL containers +std::vector v; +std::unordered_map m; + +// automatic transformation +json j_array(v); +json j_object(m); + +// works both ways +auto l = j_array.get>(); +auto u = j_object + .get>(); +``` + +# Type conversion + +- convert your own types to and from JSON +- one macro writes to_json and from_json +- or write both functions for full control + +```cpp +// some user-defined type +struct person { + std::string name; + int age; +}; + +// defines to_json and from_json +NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(person, + name, age) + +// conversion in action +person p {"Ned Flanders", 60}; +json j = p; +// {"age": 60, "name": "Ned Flanders"} + +auto p2 = j.get(); +``` + +# Type deduction + +- types are deduced automatically +- member functions tell you the type + +```cpp +// type deduction +json j1 = nullptr; +json j2 = false; +json j3 = 12.23; +json j4 = {1, 2, 3}; +json j5 = {{"key", "value"}}; +json j6 = "Hello"; + +// quick checks +j1.is_null(); +j2.is_boolean(); +j3.is_number(); +j4.is_array(); +j5.is_object(); +j6.is_string(); +``` + +# Serialization + +- to a string or a stream +- pretty-printing included + +```cpp +// default serialization +auto s1 = j.dump(); + +// pretty-printed +int indent = 4; +std::string s2 = j.dump(indent); + +// to stream +std::cout << j << std::endl; + +// pretty-printed +std::cout << std::setw(indent) << j << std::endl; +``` + +# Deserialization + +- parse strings, streams and files +- the _json literal puts plain JSON in your code +- comments and trailing commas on request + +```cpp +// from string +json j1 = json::parse("[1,null,false]"); + +// from file +std::ifstream f("example.json"); +json j2 = json::parse(f); + +// from string literal +json j3 = "[3.14159]"_json; + +// from raw string literal +json j4 = R"( + {"one": 1, "two": 2, "three": [1, 2, 3]} +)"_json; +``` + +# SAX parsing + +- the parser can emit SAX events +- process input without building a JSON value + +```cpp +// define a SAX event processor +struct sax_processor : json::json_sax_t +{ + bool boolean(bool val) override + { + return true; + } + + // implement other functions... +}; + +// create object +sax_processor my_processor; + +// use it to process input +json::sax_parse(input, &my_processor); +``` + +# Zero-copy views {badge: NEW IN 3.13} + +- parse without building a tree: 16 bytes per value +- strings and numbers stay in the source text +- materialize a json value only where you need one + +```cpp +#include + +// parse into a flat index, not a tree +std::ifstream f("big.json"); +auto doc = nlohmann::json_document::parse(f); + +// read-only views with familiar access +auto view = doc.root(); +double pi = view["pi"].get(); +for (auto v : view["list"]) + std::cout << v.get() << '\n'; + +// build a json value only where needed +json part = view["list"].materialize(); +``` + +# JSON Pointer + +- address any element with a JSON Pointer +- flatten a value to pointer/value pairs, and back + +```cpp +json j = {"good", "bad", "ugly"}; + +// JSON pointer to the 3rd list element +auto ptr = json::json_pointer("/2"); + +// use JSON pointer to access element +j[ptr] = {{"it", "cattivo"}}; + +// user-defined string literal +j["/2/en"_json_pointer] = "ugly"; +// ["good","bad",{"en":"ugly","it":"cattivo"}] + +json flat = j.flatten(); +// {"/0":"good","/1":"bad","/2/en":"ugly", +// "/2/it":"cattivo"} + +json j2 = flat.unflatten(); // j == j2 +``` + +# JSON Patches + +- use JSON Patch to describe edit operations +- create diffs from two JSON values +- or use a JSON Merge Patch that looks like the document + +```cpp +json source = R"({"F": "Paris", "D": "Berlin"})"_json; + +// a JSON Patch (RFC 6902) +json p1 = R"([ + {"op": "add", "path": "/GB", "value": "London"} +])"_json; +json target = source.patch(p1); + +// create a diff from two JSON values +json p2 = json::diff(target, source); +// [{"op": "remove", "path": "/GB"}] + +// a JSON Merge Patch (RFC 7396) +target.merge_patch(R"({"F": null})"_json); +// {"D": "Berlin", "GB": "London"} +``` + +# Binary formats + +- compact binary representations +- BJData, BON8, BSON, CBOR, MessagePack and UBJSON + +```cpp +// a JSON value +json j = R"({"compact": true, "schema": 0})"_json; + +// serialize to CBOR +std::vector v = json::to_cbor(j); +// 0xA2, 0x67, 0x63, 0x6F, 0x6D, 0x70, 0x61, +// 0x63, 0x74, 0xF5, 0x66, 0x73, 0x63, 0x68, +// 0x65, 0x6D, 0x61, 0x00 + +// roundtrip +json j_from_cbor = json::from_cbor(v); + +// the same for the other formats +auto b = json::to_bon8(j); +auto m = json::to_msgpack(j); +``` + +# Algorithms + +- the standard algorithms just work +- sort, find, count, merge and more + +```cpp +// sort JSON containers +std::sort(j.begin(), j.end()); + +// find element +auto pos = std::find(j.begin(), j.end(), + json("needle")); + +// count all numbers +auto n = std::count_if(j.begin(), j.end(), + [](const json& v) { return v.is_number(); }); + +// merge two sorted containers +std::merge(j1.begin(), j1.end(), + j2.begin(), j2.end(), + std::back_inserter(j3)); +``` + +# Iterators + +- iterators, const iterators, and reverse iterators +- random access for arrays +- items() gives you keys and values + +```cpp +json j = {1, 2, 3, 4, 5}; + +// range for +for (auto& el : j) +{ + // print all values + std::cerr << el << '\n'; +} + +// classical iterators +for (auto it = j.begin(); it != j.end(); ++it) +{ + // double all values + *it = it->get() * 2; +} +``` + +# Structured bindings + +- since C++17, unpack keys and values with structured bindings + +```cpp +// JSON object +auto j = R"({"foo": 23, "bar": 42})"_json; + +// structured bindings +for (auto& [key, val] : j.items()) +{ + std::cerr << key << ':' << val << '\n'; +} +```