# core/normalizers/field.normalizer.ts

> 56 lines of code and 14 definitions.

Tree: GovLab Context
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/context#file-context-core-normalizers-field-normalizer-ts
Source text: https://banes-lab.com/source/context/core/normalizers/field.normalizer.ts.txt

Listed in [core/normalizers](https://banes-lab.com/api/source/context/core/normalizers.md), after [core/normalizers/architecture.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/architecture.normalizer.ts.md) and before [core/normalizers/grammar.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/grammar.normalizer.ts.md).

## Definitions

- `asString` (lexical_declaration, line 6, exported)
- `asStringArray` (lexical_declaration, line 10, exported)
- `optionalString` (lexical_declaration, line 58, exported)
- `optionalStrings` (lexical_declaration, line 50, exported)
- `asClosed` (lexical_declaration, line 14, exported)
- `asClosedArray` (lexical_declaration, line 23, exported)
- `asDistinctFrom` (lexical_declaration, line 31, exported)
- `asExemplar` (lexical_declaration, line 37, exported)
- `text` (lexical_declaration, line 15, exported)
- `found` (lexical_declaration, line 16, exported)
- `before` (lexical_declaration, line 41, exported)
- `after` (lexical_declaration, line 42, exported)
- `medium` (lexical_declaration, line 46, exported)
- `value` (lexical_declaration, line 62, exported)

## Contained in

- [core/normalizers](https://banes-lab.com/anatomy/context/folder-context-core-normalizers.md)

## Uses

- [configuration/strings/field.strings.ts](https://banes-lab.com/source/context/configuration/strings/field.strings.ts.md)
- [core/predicates/record.predicate.ts](https://banes-lab.com/source/context/core/predicates/record.predicate.ts.md)

## Used by

- [core/normalizers/algorithm.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/algorithm.normalizer.ts.md)
- [core/normalizers/architecture.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/architecture.normalizer.ts.md)
- [core/normalizers/grammar.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/grammar.normalizer.ts.md)
- [core/normalizers/lexicon.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/lexicon.normalizer.ts.md)
- [core/normalizers/reason.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/reason.normalizer.ts.md)
- [core/normalizers/reason.surface.normalizer.ts](https://banes-lab.com/source/context/core/normalizers/reason.surface.normalizer.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/context/folder-context-configuration-strings.md)
- [core/normalizers](https://banes-lab.com/anatomy/context/folder-context-core-normalizers.md)
- [core/predicates](https://banes-lab.com/anatomy/context/folder-context-core-predicates.md)

## Source

```typescript
import { COMPOSITE_MEDIUM, DEFAULT_EXEMPLAR_LANG } from "#configuration/constants/field.constants";
import type { DistinctDeclaration, Exemplar, ExemplarMedium } from "#types/field.types";
import { isObject } from "#core/predicates/record.predicate";
import { outsideVocabulary } from "#configuration/strings/field.strings";

export const asString = function asString(value: unknown): string {
    return typeof value === "string" ? value : "";
};

export const asStringArray = function asStringArray(value: unknown): string[] {
    return Array.isArray(value) ? value.filter((item): item is string => typeof item === "string") : [];
};

export const asClosed = function asClosed<T extends string>(values: readonly T[], value: unknown, subject: string): T {
    const text = asString(value);
    const found = values.find((entry) => entry === text);
    if (found === undefined) {
        throw new Error(outsideVocabulary(subject, text, values));
    }
    return found;
};

export const asClosedArray = function asClosedArray<T extends string>(
    values: readonly T[],
    value: unknown,
    subject: string,
): T[] {
    return asStringArray(value).map((entry) => asClosed(values, entry, subject));
};

export const asDistinctFrom = function asDistinctFrom(value: unknown): DistinctDeclaration[] {
    return (Array.isArray(value) ? value : [])
        .filter(isObject)
        .map((entry) => ({ id: asString(entry["id"]), reason: asString(entry["reason"]) }));
};

export const asExemplar = function asExemplar(value: unknown): Exemplar | null {
    if (!isObject(value)) {
        return null;
    }
    const before = asString(value["before"]);
    const after = asString(value["after"]);
    if (!before && !after) {
        return null;
    }
    const medium: ExemplarMedium = asString(value["medium"]) === COMPOSITE_MEDIUM ? "composite" : "code";
    return { after, before, lang: asString(value["lang"]) || DEFAULT_EXEMPLAR_LANG, medium };
};

export const optionalStrings = function optionalStrings(
    raw: Record<string, unknown>,
    key: string,
): Record<string, string[]> {
    const value = asStringArray(raw[key]);
    return value.length > 0 ? { [key]: value } : {};
};

export const optionalString = function optionalString(
    raw: Record<string, unknown>,
    key: string,
): Record<string, string> {
    const value = asString(raw[key]);
    return value ? { [key]: value } : {};
};
```
