Table of Contents

Numeric Input/Output

NaN, Infinity and -Infinity are ordinary values in a geometry model and ordinary things for a user to type, but JSON has no encoding for any of them. This page describes how the web application carries them across the boundary in both directions.

The Server Half

One line of configuration does it. Program.cs sets

options.JsonSerializerOptions.NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals;

so the ASP.NET Core serializer reads and writes the three values as the JSON string literals "NaN", "Infinity" and "-Infinity" instead of failing. It sits on the AddControllers JSON options, so every controller inherits it and no DTO, converter or endpoint has to opt in. Without it a double.NaN anywhere in a response throws during serialization, which is why the setting is load-bearing rather than a convenience.

The reach stops at the controllers. Hub payloads are serialized by SignalR's own protocol options, and nothing configures them: AddSignalR() is registered bare and the service contains no AddJsonProtocol call, so the named literals are a controller-JSON contract rather than a service-wide one.

The Client Half

There is no shared numeric module. Each input widget formats and parses the special values itself, and the three that ship do not agree on all of it. The widgets are also not the only readers of this boundary: an API module that has to put a non-finite number on the wire converts it in place, as wwwroot-src/src/api/mission.ts does for the mission command fields. That conversion is not symmetric — it writes all three literals but recognises only the two infinity spellings on the way back — so a NaN returned by those endpoints resolves to the caller's supplied default instead.

NumericInput.vue Vec3Input.vue Mat4Input.vue
NaN displays as empty field NaN 0
Infinity / -Infinity display as literal text literal text literal text
An empty field parses to null, under the default allowEmpty 0 0
Unparseable text on blur stays in the box under an error message reverts to the last valid value reverts to the last valid value

All three accept the same spellings on the way in: infinity, -infinity and nan case-insensitively. The and -∞ glyphs are where they part: the single-value field and the three-axis editor take them, the matrix grid does not. All three commit on blur or Enter rather than per keystroke, but only the vector and matrix editors compare the parsed value against the model before emitting. The single-value field carries no such guard and emits on every accepted commit, so committing with Enter and then leaving the field writes the same value twice.

The consequence worth knowing: NaN does not survive a round trip through the matrix editor. Mat4Input.vue renders it as 0, so re-committing a cell that held NaN writes a real zero.

NumericInput.vue is the input the rest of the app reaches for: no other component in the client is embedded by anywhere near as many editors, so a new numeric field should embed it rather than repeat the parse. Its own props, bounds and validation messages are documented at Numeric Input.

Source Code Path

See HiNC App Anatomy for git repository links.

Web Page Application Source Code Path

  • wwwroot-src/src/components/widgets/NumericInput.vue — the shared single-value numeric field: local formatValue / parseValue, blur-and-Enter commit, and the rejected text left standing under an error message when a commit fails.
  • wwwroot-src/src/components/widgets/Vec3Input.vue — the three-axis editor; carries its own copy of the same format and parse pair, plus a (x, y, z) text form.
  • wwwroot-src/src/components/widgets/Mat4Input.vue — the sixteen-cell matrix grid; the same pair again without the glyph spellings, and with the NaN-to-zero behaviour above.
  • wwwroot-src/src/api/mission.ts — a non-widget reader of the same boundary: numericToApiString writes all three literals for the mission command fields, parseMaybeInfiniteNumber reads only the two infinity spellings back.
  • Program.cs — the AllowNamedFloatingPointLiterals setting on the controller JSON options that lets the three values cross as JSON at all, and the bare AddSignalR() registration that does not share it.

See Also

  • Vec3dControl Component — the three-axis editor, one of the three inputs that carries its own format and parse
  • Mat4dControl Component — the sixteen-cell grid, the one input that renders NaN as zero
  • Numeric Input — the single-value field, and the widget that carries this contract to the most callers