JSON in depth
Software & Tech Stackarticle · 11 मिनट · अपडेट 19 जुल॰ 2026

JSON in depth

लेखक Rajendra Sharma, RN, CPC, CPBसमीक्षक Rajendra Sharma, RN, CPC, CPB · 19 जुल॰ 2026

Past the syntax: the six value types, why null and missing and empty string are three different clinical statements, nested structures, FHIR Bundles, JSON Schema, NDJSON — and the four errors that break real payloads.

JSONJSON SchemaNDJSONFHIR

In one line

JSON's syntax takes ten minutes to learn; its semantics — especially what an absent field means about a patient — take rather longer, and that is where the clinical bugs live.

This entry assumes you have read HTTP & JSON for health APIs, which covers the transport, status codes, and the identifier-precision trap. Here we go deeper into the data model itself.

The six value types, exactly

A JSON value is one of six things. That's the whole type system.

{
  "resourceType": "Observation",
  "id": "obs-weight-1",
  "status": "final",
  "valueQuantity": { "value": 61.4, "unit": "kg" },
  "category": [{ "text": "vital-signs" }],
  "issued": "2026-07-19T10:30:00+05:30",
  "_note": null,
  "hasMember": []
}

object, array, string, number, true/false (boolean), null. Notice what is not there: no date, no decimal, no integer-vs-float distinction, no binary, no comments, no reference to another node. "2026-07-19T10:30:00+05:30" is a string and nothing validates it as a date unless you make that happen.

Numbers are the classic trap. JSON's grammar allows arbitrary precision, but virtually every parser reads a number into an IEEE 754 double. 61.4 is not exactly 61.4. For a weight that is harmless; for a currency amount in a claim, or a 14-digit identifier, it is not. Identifiers and money are strings.

null vs missing vs empty string — the clinical one

This is the most important paragraph in this entry.

{ "smokingStatus": null }
{ }
{ "smokingStatus": "" }

Three different payloads. Naive code treats all three as "no smoking status" and moves on. Clinically they are three different statements, and a fourth statement — the important one — is missing from all of them.

  • Field absent — the system has nothing to say. Often it was never asked, but it may also be that this API simply doesn't carry the field.
  • null — the field exists in this record and has no value. Usually means "unknown".
  • "" — a value was captured and it is the empty string. In practice this is nearly always a data-entry artefact or a bad export, not a clinical fact. Treat it as suspicious.
  • Known to be absent — the clinician asked and the answer was no. This is a positive clinical finding and JSON has no way to express it with an empty field.

That last one decides patient safety. "No known allergies" is not the same as "no allergy data". A blank allergy list on screen reads to a clinician as safe to prescribe, and if the truth was "we never received the allergy section", your UI just made a clinical claim your data does not support.

FHIR handles this with two explicit mechanisms rather than blanks.

For why a value is missing, the data-absent-reason extension, whose codes are exactly the distinctions above:

{
  "resourceType": "Observation",
  "status": "final",
  "code": {
    "coding": [{
      "system": "http://loinc.org",
      "code": "29463-7",
      "display": "Body weight"
    }]
  },
  "_valueQuantity": {
    "extension": [{
      "url": "http://hl7.org/fhir/StructureDefinition/data-absent-reason",
      "valueCode": "asked-unknown"
    }]
  }
}

The value set includes not-asked, asked-unknown, asked-declined, temp-unknown, masked, not-applicable and more, from the code system http://terminology.hl7.org/CodeSystem/data-absent-reason. The leading-underscore _valueQuantity is FHIR's JSON convention for attaching extensions to a primitive or absent element — it is a FHIR rule, not a JSON one.

For known absence, you assert it with a code. "No known allergy" is SNOMED CT 716186003, recorded as an actual AllergyIntolerance resource — a positive record saying nothing was found, which is queryable, attributable and datable in a way that a blank never is.

Design rule: if your model cannot distinguish "not asked" from "asked and unknown" from "known to be absent", your model is wrong for clinical data. Add the distinction before you add features.

Arrays of objects, and nesting

Health data is deeply nested because reality is. The pattern you will meet constantly is an array of objects, where each element is a coded thing with its own metadata.

{
  "resourceType": "Patient",
  "id": "meera-iyer",
  "identifier": [
    {
      "system": "http://example.org/fhir/sid/abha",
      "value": "91-7412-8523-6900"
    }
  ],
  "name": [
    { "use": "official", "family": "Iyer", "given": ["Meera"] }
  ],
  "gender": "female",
  "birthDate": "1989-04-12",
  "address": [
    { "city": "Pune", "state": "Maharashtra", "country": "IN" }
  ]
}

Three things to internalise.

Almost everything that can repeat, does. name, identifier, address, telecom are arrays. Writing patient.name.family works on your test payload and throws on the first real patient. Write patient.name?.[0]?.family — or better, pick by use.

Order in an array is meaningful in JSON and usually not meaningful in FHIR. The array preserves order; the spec rarely promises the first entry is the primary one. Don't assume given[0] is what the patient is called.

Order of keys is not guaranteed at all. Do not hash or sign raw JSON without canonicalising it first, and do not diff two payloads by string comparison.

FHIR Bundles

A Bundle is the container for many resources in one JSON document — search results, transactions, documents, messages. Its shape is fixed and worth memorising.

{
  "resourceType": "Bundle",
  "type": "searchset",
  "total": 2,
  "link": [
    { "relation": "self", "url": "https://example.org/fhir/Observation?patient=meera-iyer" },
    { "relation": "next", "url": "https://example.org/fhir/Observation?patient=meera-iyer&page=2" }
  ],
  "entry": [
    {
      "fullUrl": "https://example.org/fhir/Observation/obs-1",
      "resource": {
        "resourceType": "Observation",
        "id": "obs-1",
        "status": "final",
        "subject": { "reference": "Patient/meera-iyer" }
      },
      "search": { "mode": "match" }
    }
  ]
}

Two habits. Follow the next link — a Bundle is a page, and code that reads entry once and stops has silently truncated the patient's record. And check search.mode: entries with mode include came along via _include and are context, not results. total is the count of matches, which is not entry.length.

JSON Schema

JSON has no built-in validation, so JSON Schema supplies it — a schema is itself JSON.

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://example.org/schemas/vital.json",
  "type": "object",
  "required": ["patientId", "code", "effectiveDateTime"],
  "additionalProperties": false,
  "properties": {
    "patientId": { "type": "string", "pattern": "^[A-Za-z0-9-]+$" },
    "code": { "type": "string", "enum": ["29463-7", "8302-2"] },
    "effectiveDateTime": { "type": "string", "format": "date-time" },
    "value": { "type": ["number", "null"] },
    "dataAbsentReason": {
      "type": "string",
      "enum": ["not-asked", "asked-unknown", "asked-declined"]
    }
  }
}

LOINC 29463-7 is body weight and 8302-2 is body height. Note "type": ["number", "null"] — that is how you say nullable and it is the schema-level admission that a missing measurement is a real state. Note additionalProperties: false, which turns a typo'd field name into an error instead of silent data loss.

The honest caveat: FHIR does not validate with JSON Schema. FHIR publishes one for tooling convenience, but real conformance checking uses StructureDefinitions and the FHIR validator, because JSON Schema cannot express terminology bindings, invariants or profile slicing. Use JSON Schema for your own internal payloads; use the FHIR validator for FHIR.

NDJSON

Newline-delimited JSON: one complete JSON value per line, no wrapping array, no commas.

{"resourceType":"Patient","id":"meera-iyer","gender":"female"}
{"resourceType":"Patient","id":"karen-whitfield","gender":"female"}
{"resourceType":"Observation","id":"obs-1","status":"final"}

Why it exists: a two-million-patient export as a single JSON array must be fully parsed before you can touch record one. NDJSON streams — read a line, parse it, process it, forget it, in constant memory. A truncated file also costs you one record instead of everything.

This is exactly why FHIR Bulk Data Access uses it. The $export operation returns NDJSON files, one per resource type, with media type application/fhir+ndjson (servers also accept the shorter application/ndjson and ndjson as _outputFormat values). Each line is one complete resource — not a Bundle. Population-scale work — quality measures, registries, analytics — moves this way.

Rules: no newline inside a line (escape as \n within strings), UTF-8, and never load the file with a whole-file parser.

The four errors that break real payloads

  1. Trailing comma. {"a": 1, "b": 2,} is invalid JSON. JavaScript object literals allow it; JSON does not. Hand-edited config and templated output are where this appears.
  2. Single quotes, or unquoted keys. {'name': 'Meera'} and {name: "Meera"} are both invalid. JSON strings use double quotes, and keys are always quoted strings.
  3. Unescaped characters inside strings. A literal newline, tab, backslash or double quote inside a string value is invalid — a clinical note pasted straight into a field is the usual culprit. Escape as \n, \t, \\, \". Let your serialiser do it; never build JSON by string concatenation.
  4. Non-JSON numeric literals. NaN, Infinity, -Infinity and hex are not valid JSON, and neither is a leading + or a leading zero like 007. A division-by-zero producing NaN in your calculation will emit a payload no conformant parser accepts — which is why FHIR has not-a-number and the infinities as data-absent-reason codes.

A fifth, quieter one: duplicate keys. RFC 8259 says names should be unique but doesn't forbid repeats, so parsers disagree — most keep the last, some the first, some error. Never emit them; if you receive them, decide explicitly.

Knowing this and catching it in a 40 KB payload at 2 a.m. are different skills. The data-formats lab hands you broken and ambiguous JSON — trailing commas, blank allergy lists, truncated Bundles, an NDJSON export to stream — and asks you to say what the patient record actually claims.

संदर्भ

  1. IETF — RFC 8259: The JavaScript Object Notation (JSON) Data Interchange Format
  2. JSON Schema — Specification
  3. HL7 FHIR R4 — Bundle
  4. HL7 FHIR Bulk Data Access IG — Export
  5. HL7 FHIR R4 — Data Absent Reason extension

संबंधित entries