Skip to content

Commit cf077f7

Browse files
committed
Return the narrowed value in the result, and remove properties and items from object and array types
1 parent 1e1d9ea commit cf077f7

21 files changed

+224
-374
lines changed

README.md

+19-96
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
- [🚀 Features](#-features)
1111
- [💻 Usage](#-usage)
12-
- [Objects](#objects)
13-
- [Arrays](#arrays)
1412
- [Strings](#strings)
1513
- [String Formats](#string-formats)
1614
- [Date/Time strings](#datetime-strings)
@@ -21,7 +19,7 @@
2119
## 🚀 Features
2220

2321
- Written in typescript
24-
- Infers types of values inside objects and arrays
22+
- Narrows type of the value when using with Typescript
2523
- Lightweight with only a few third-party dependencies
2624
- Includes a large set of formats for strings
2725
- Dates and times (and timestamps)
@@ -51,103 +49,20 @@ $ npm install --save @jsonhero/json-infer-types
5149
```js
5250
const { inferType } = require("@jsonhero/json-infer-types");
5351

54-
inferType(123); // => { name: "int" }
52+
inferType(123); // => { name: "int", value: 123 }
5553
```
5654

57-
The following basic types are supported:
55+
The following types are supported:
5856

5957
```js
60-
inferType(null); // => { name: "null" }
61-
inferType(true); // => { name: "bool" }
62-
inferType(123); // => { name: "int" }
63-
inferType(123.456); // => { name: "float" }
64-
inferType("hello world"); // => { name: "string" }
65-
```
66-
67-
### Objects
68-
69-
Objects have an additional `properties` property that infers its value types
70-
71-
```js
72-
inferType({ foo: "bar" });
73-
```
74-
75-
Will result in
76-
77-
```json
78-
{ "name": "object", "properties": { "foo": { "name": "string" } } }
79-
```
80-
81-
### Arrays
82-
83-
Arrays have an additional `items` property that infers the types of its items
84-
85-
```js
86-
inferType([8, 176, 3, 49, 0]); // { name: "array", items: { name: "int" }}
87-
```
88-
89-
This works for an array of objects as well
90-
91-
```js
92-
inferType([
93-
{ id: "1", email: "[email protected]" },
94-
{ id: "2", email: "[email protected]" },
95-
]);
96-
```
97-
98-
Will result in
99-
100-
```json
101-
{
102-
"name": "array",
103-
"items": {
104-
"name": "object",
105-
"properties": {
106-
"id": { "name": "string" },
107-
"email": {
108-
"name": "string",
109-
"format": {
110-
"name": "email",
111-
"variant": "rfc5321"
112-
}
113-
}
114-
}
115-
}
116-
}
117-
```
118-
119-
If they array has items of different types, `items` will be an array of objects representing each unique type found in the array
120-
121-
```js
122-
inferType([1, "hello world"]);
123-
```
124-
125-
Gives the result
126-
127-
```json
128-
{
129-
"name": "array",
130-
"items": [
131-
{
132-
"name": "int"
133-
},
134-
{
135-
"name": "string"
136-
}
137-
]
138-
}
139-
```
140-
141-
If you don't want or need the `properties` or `items` inferred you can pass the `shallow: true` option to `inferType`
142-
143-
```js
144-
inferType(
145-
[
146-
{ id: "1", email: "[email protected]" },
147-
{ id: "2", email: "[email protected]" },
148-
],
149-
{ shallow: true }
150-
); // => { name: "array" }
58+
inferType(null); // => { name: "null", value: null }
59+
inferType(undefined); // => { name: "null", value: null }
60+
inferType(true); // => { name: "bool", value: true }
61+
inferType(123); // => { name: "int", value: 123 }
62+
inferType(123.456); // => { name: "float", value: 123.456 }
63+
inferType("hello world"); // => { name: "string", value: "hello world" }
64+
inferType({ foo: "bar" }); // => { name: "object", value: { foo: "bar" } }
65+
inferType([1, 2, 3]); // => { name: "array", value: [1, 2, 3] }
15166
```
15267

15368
### Strings
@@ -163,6 +78,7 @@ Will be
16378
```json
16479
{
16580
"name": "string",
81+
"value": "https://www.example.com/foo#bar",
16682
"format": {
16783
"name": "uri"
16884
}
@@ -174,6 +90,7 @@ Some formats have mutliple variants, like IP Address. `inferType("192.168.0.1")`
17490
```json
17591
{
17692
"name": "string",
93+
"value": "192.168.0.1",
17794
"format": {
17895
"name": "ip",
17996
"variant": "v4"
@@ -186,6 +103,7 @@ And `inferType("2001:db8:1234::1")` will be interpreted as an IPV6 address
186103
```json
187104
{
188105
"name": "string",
106+
"value": "2001:db8:1234::1",
189107
"format": {
190108
"name": "ip",
191109
"variant": "v6"
@@ -208,6 +126,7 @@ Will result in
208126
```json
209127
{
210128
"name": "string",
129+
"value": "2019-01-01 00:00:00.000Z",
211130
"format": {
212131
"name": "datetime",
213132
"parts": "datetime",
@@ -245,6 +164,7 @@ Will result in
245164
```json
246165
{
247166
"name": "string",
167+
"value": "1596597629980",
248168
"format": {
249169
"name": "timestamp",
250170
"variant": "millisecondsSinceEpoch"
@@ -267,6 +187,7 @@ Will result in
267187
```json
268188
{
269189
"name": "string",
190+
"value": "https://www.example.com/foo#bar",
270191
"format": {
271192
"name": "uri"
272193
}
@@ -278,6 +199,7 @@ If the URI contains a file extension, the inferred `contentType` will be include
278199
```json
279200
{
280201
"name": "string",
202+
"value": "https://www.example.com/foo.json",
281203
"format": {
282204
"name": "uri",
283205
"contentType": "application/json"
@@ -300,6 +222,7 @@ Will result in
300222
```json
301223
{
302224
"name": "string",
225+
"value": "[email protected]",
303226
"format": {
304227
"name": "email",
305228
"variant": "rfc5321"

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@jsonhero/json-infer-types",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"description": "Infer the types of a JSON value",
55
"homepage": "https://github.com/jsonhero-io/json-infer-types",
66
"bugs": {

src/@types.ts

+8-87
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,39 @@
1-
export type JSONDateTimeFormat = {
2-
name: "datetime";
3-
parts: "datetime" | "date" | "time";
4-
variant: "rfc2822" | "rfc3339";
5-
};
6-
7-
export type JSONTimestampFormat = {
8-
name: "timestamp";
9-
variant: "millisecondsSinceEpoch" | "nanosecondsSinceEpoch" | "secondsSinceEpoch";
10-
};
11-
12-
export type JSONEmailFormat = {
13-
name: "email";
14-
variant: "rfc5321" | "rfc5322";
15-
};
16-
17-
export type JSONCurrencyFormat = {
18-
name: "currency";
19-
variant: "iso4217" | "english" | "crypto";
20-
};
21-
22-
export type JSONCountryFormat = {
23-
name: "country";
24-
variant: "iso3166-2" | "iso3166-3";
25-
};
26-
27-
export type JSONTLDFormat = {
28-
name: "tld";
29-
};
30-
31-
export type JSONIPAddressFormat = {
32-
name: "ip";
33-
variant: "v4" | "v6";
34-
};
35-
36-
export type JSONLanguageFormat = {
37-
name: "language";
38-
variant: "iso693-1" | "iso693-2" | "english" | "native";
39-
};
40-
41-
export type JSONPhoneNumberFormat = {
42-
name: "phoneNumber";
43-
variant: "e.164";
44-
};
45-
46-
export type JSONURIFormat = {
47-
name: "uri";
48-
contentType?: string;
49-
};
50-
51-
export type JSONUUIDFormat = {
52-
name: "uuid";
53-
variant: "v1" | "v4" | "v5";
54-
};
55-
56-
export type JSONHostnameFormat = {
57-
name: "hostname";
58-
variant: "rfc1123" | "rfc5890";
59-
};
60-
61-
export type JSONFilesizeFormat = {
62-
name: "filesize";
63-
variant: "human";
64-
};
65-
66-
export type JSONJSONFormat = {
67-
name: "json";
68-
variant: "ecma262" | "json5";
69-
};
70-
71-
export type JSONStringFormat =
72-
| JSONHostnameFormat
73-
| JSONUUIDFormat
74-
| JSONURIFormat
75-
| JSONPhoneNumberFormat
76-
| JSONLanguageFormat
77-
| JSONIPAddressFormat
78-
| JSONTLDFormat
79-
| JSONCountryFormat
80-
| JSONCurrencyFormat
81-
| JSONEmailFormat
82-
| JSONTimestampFormat
83-
| JSONDateTimeFormat
84-
| JSONFilesizeFormat
85-
| JSONJSONFormat;
1+
import { JSONStringFormat } from "./formats";
862

873
export type JSONNullType = {
884
name: "null";
5+
value: null;
896
};
907

918
export type JSONBoolType = {
929
name: "bool";
10+
value: boolean;
9311
};
9412

9513
export type JSONFloatType = {
9614
name: "float";
15+
value: number;
9716
};
9817

9918
export type JSONIntType = {
10019
name: "int";
20+
value: number;
10121
};
10222

10323
export type JSONStringType = {
10424
name: "string";
10525
format?: JSONStringFormat;
26+
value: string;
10627
};
10728

10829
export type JSONObjectType = {
10930
name: "object";
110-
properties?: Record<string, JSONValueType>;
31+
value: object;
11132
};
11233

11334
export type JSONArrayType = {
11435
name: "array";
115-
items?: JSONValueType | JSONValueType[];
36+
value: Array<unknown>;
11637
};
11738

11839
export type JSONValueType =

src/formats/country.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { JSONCountryFormat } from "../@types";
1+
export type JSONCountryFormat = {
2+
name: "country";
3+
variant: "iso3166-2" | "iso3166-3";
4+
};
25

36
const iso31663Regex =
47
/^(?:(AFG|ALB|DZA|ASM|AND|AGO|AIA|ATA|ATG|ARG|ARM|ABW|AUS|AUT|AZE|BHS|BHR|BGD|BRB|BLR|BEL|BLZ|BEN|BMU|BTN|BOL|BES|BIH|BWA|BVT|BRA|IOT|BRN|BGR|BFA|BDI|CPV|KHM|CMR|CAN|CYM|CAF|TCD|CHL|CHN|CXR|CCK|COL|COM|COD|COG|COK|CRI|HRV|CUB|CUW|CYP|CZE|CIV|DNK|DJI|DMA|DOM|ECU|EGY|SLV|GNQ|ERI|EST|SWZ|ETH|FLK|FRO|FJI|FIN|FRA|GUF|PYF|ATF|GAB|GMB|GEO|DEU|GHA|GIB|GRC|GRL|GRD|GLP|GUM|GTM|GGY|GIN|GNB|GUY|HTI|HMD|VAT|HND|HKG|HUN|ISL|IND|IDN|IRN|IRQ|IRL|IMN|ISR|ITA|JAM|JPN|JEY|JOR|KAZ|KEN|KIR|PRK|KOR|KWT|KGZ|LAO|LVA|LBN|LSO|LBR|LBY|LIE|LTU|LUX|MAC|MDG|MWI|MYS|MDV|MLI|MLT|MHL|MTQ|MRT|MUS|MYT|MEX|FSM|MDA|MCO|MNG|MNE|MSR|MAR|MOZ|MMR|NAM|NRU|NPL|NLD|NCL|NZL|NIC|NER|NGA|NIU|NFK|MNP|NOR|OMN|PAK|PLW|PSE|PAN|PNG|PRY|PER|PHL|PCN|POL|PRT|PRI|QAT|MKD|ROU|RUS|RWA|REU|BLM|SHN|KNA|LCA|MAF|SPM|VCT|WSM|SMR|STP|SAU|SEN|SRB|SYC|SLE|SGP|SXM|SVK|SVN|SLB|SOM|ZAF|SGS|SSD|ESP|LKA|SDN|SUR|SJM|SWE|CHE|SYR|TWN|TJK|TZA|THA|TLS|TGO|TKL|TON|TTO|TUN|TUR|TKM|TCA|TUV|UGA|UKR|ARE|GBR|UMI|USA|URY|UZB|VUT|VEN|VNM|VGB|VIR|WLF|ESH|YEM|ZMB|ZWE|ALA))$/;

src/formats/currency.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { JSONCurrencyFormat } from "../@types";
1+
export type JSONCurrencyFormat = {
2+
name: "currency";
3+
variant: "iso4217" | "english" | "crypto";
4+
};
25

36
const iso4217Regex =
47
/^(?:(AED|AFN|ALL|AMD|ANG|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BGN|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTC|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HRK|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLL|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWL))$/;

src/formats/datetime.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { JSONDateTimeFormat } from "../@types";
1+
export type JSONDateTimeFormat = {
2+
name: "datetime";
3+
parts: "datetime" | "date" | "time";
4+
variant: "rfc2822" | "rfc3339";
5+
};
26

37
export function inferDatetime(value: string): JSONDateTimeFormat | undefined {
48
const rfc3339Match = inferRFC3339(value);

src/formats/email.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { JSONStringFormat } from "../@types";
1+
export type JSONEmailFormat = {
2+
name: "email";
3+
variant: "rfc5321" | "rfc5322";
4+
};
25

36
const rfc5321AddressRegex =
47
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i;
@@ -7,7 +10,7 @@ const rfc5321Regex = new RegExp(`^${rfc5321AddressRegex.source}$`);
710

811
const rfc5322Regex = new RegExp(`^[^"]+<${rfc5321AddressRegex.source}>$`);
912

10-
export function inferEmail(value: string): JSONStringFormat | undefined {
13+
export function inferEmail(value: string): JSONEmailFormat | undefined {
1114
if (rfc5321Regex.exec(value)) {
1215
return { name: "email", variant: "rfc5321" };
1316
}

src/formats/filesize.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { JSONFilesizeFormat } from "../@types";
1+
export type JSONFilesizeFormat = {
2+
name: "filesize";
3+
variant: "human";
4+
};
25

36
export function inferFilesize(value: string): JSONFilesizeFormat | undefined {
47
if (value.match(/^[0-9.]+\s?(?:(B|MB|K|GB|TB|PB|MiB|KB|kB))$/)) {

src/formats/hostname.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { JSONHostnameFormat } from "../@types";
1+
export type JSONHostnameFormat = {
2+
name: "hostname";
3+
variant: "rfc1123" | "rfc5890";
4+
};
25

36
function isValidHostname(value: string, allowUnderscore = false): boolean {
47
if (value.length === 0) {

0 commit comments

Comments
 (0)