Skip to content

[P3 bug] null on optional:true numeric field is rejected, while omission is accepted — asymmetric absence #22

@Sam-Bolling

Description

@Sam-Bolling

Summary

For a numeric Quantity field with optional: true, omitting the field is accepted (HTTP 201) but sending it with null is rejected (HTTP 400 must be a number). Both forms are user intent for "no value here", and the asymmetry is undocumented and confusing.

Reproduction

Datastream with baro_altitude declared as Quantity with "optional": true.

T14: POST observations  body={"result":{"geo_altitude":10500}}
  → HTTP 201 ✓ (omitted is OK)

T15: POST observations  body={"result":{"baro_altitude":null,"geo_altitude":10500}}
  → HTTP 400 {"error":"... result.baro_altitude must be a number"}

Full transcript: docs/research/evidence/issue-004/nan-tests-head-2026-04-30.txt (cases T14, T15).

Source

In internal/api/observation_schema_validation.go, the datarecord branch only checks for field absence to short-circuit on optional: true. When the field is present with null, control reaches the quantity branch with value = nil, the float64 type assertion fails, and the error fires.

Proposed fix

In the datarecord (and vector / dataarray) branch, treat value == nil (a JSON null) as equivalent to absence when the field is optional: true:

fieldVal, present := record[field.Name]
if (!present || fieldVal == nil) {
    if field.Optional != nil && *field.Optional { continue }
    return fmt.Errorf("%s.%s is required by datastream schema", path, field.Name)
}

Priority

P3 — UX / consistency; not a security or data-loss bug, but it forces clients to special-case absent vs explicit-null in their result encoders. Same diagnostic pattern documented in #19 for phenomenonTime vs resultTime: two ways of expressing absence should converge.

Cross-refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions