1.3.0 • Published 6 months ago

@unnoen/untypedjson v1.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

UntypedJSON

Typed JSON. The name is a lie.

npm

workflow

A simple JSON deserializer and serializer for Node.js and the browser using TypeScript decorators.

Ever been working with JSON that uses inconsistent naming conventions? Then this is the library for you!

It supports nested classes, arrays, null/undefined values, has type safety and the ability to create custom deserializers and serializers.

It also has support for Mixins, so you can extend (multiple!) classes and override properties.

Designed to be lightweight with no dependencies. Your bundler will thank you.

Offers both CommonJS and ES Module builds, so you can use it with Node.js, Webpack, Rollup, Parcel, Vite etc.

Installation

Just use your favourite package manager:

npm

npm install @unnoen/untypedjson

pnpm

pnpm add @unnoen/untypedjson

Yarn

yarn add @unnoen/untypedjson

Usage

It's super simple to use. Just decorate your class with @JsonProperty, the name of the JSON property and the type of the property. Or set a default value for it to be inferred automatically!

Then use the DeserializeObject function to parse the JSON string or object into an instance of your class.

Or use the SerializeObject function to convert your class instance into a JSON object, ready to be stringified.

Make sure you have these options enabled in your tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Simple Types

You can either use the provided JsonType enum or the built-in String, Number, and Boolean constructors so that the parser knows how to parse the value.

Or, provide a default value to infer the type automatically!

import {DeserializeObject, JsonProperty, JsonType} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', JsonType.STRING) // You can use the JsonType enum...
    public name: string;

    @JsonProperty('a', Number) // ...or the built-in constructors.
    public age: number;

    // We can set a default value to infer the type automatically!
    // In this case, the parser knows it's a string.
    @JsonProperty('o')
    public occupation = 'Unemployed';
}

const jsonString = '{"fn": "John Doe", "a": 42, "o": "Software Engineer"}';

const person = DeserializeObject(jsonString, Person);

// Now we can use our own property names!
console.log(person.name); // John Doe
console.log(person.age); // 42
console.log(person.occupation); // Software Engineer

You can also import the JsonType constants directly if that's more your style.

import {DeserializeObject, JsonProperty, STRING} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', STRING)
    public name: string;
}

// ...

Arrays

Just wrap your type in an array. The parser will automatically detect it and parse it as an array.

import {DeserializeObject, JsonProperty, JsonType} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('aka', [JsonType.STRING])
    public aliases: string[];
}

const jsonString = '{"aka": ["John", "Doe"]}';

const person = DeserializeObject(jsonString, Person);

console.log(person.aliases); // ["John", "Doe"]
console.log(person.aliases[0]); // John

Nested Classes

Just use the class as the type.

import {DeserializeObject, JsonProperty, JsonType} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;

    @JsonProperty('a', JsonType.NUMBER)
    public age: number;
}

class Company {
    @JsonProperty('cn', JsonType.STRING)
    public name: string;

    @JsonProperty('employees', [Person]) // You can also use them in arrays!
    public employees: Person[];
}

const jsonString = '{"cn": "ACME Inc.", "employees": [{"fn": "John Doe", "a": 42}]}';

const company = DeserializeObject(jsonString, Company);

console.log(company.name); // ACME Inc.
console.log(company.employees[0].name); // John Doe

Extending Classes

Just extend the class and use the @JsonProperty decorator on the properties you want to override.

import {DeserializeObject, JsonProperty, JsonType} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;

    @JsonProperty('a', JsonType.NUMBER)
    public age: number;
}

class Employee extends Person {
    @JsonProperty('s', JsonType.NUMBER)
    public salary: number;
}

const jsonString = '{"fn": "John Doe", "a": 42, "s": 100000}';

const employee = DeserializeObject(jsonString, Employee);

console.log(employee.name); // John Doe
console.log(employee.salary); // 100000

Null & Undefined Values

If you want to allow null or undefined values, just use the PropertyNullability enum.

  • PropertyNullability.MAPwill attempt to map the value to the type of the property. (default)
  • PropertyNullability.IGNORE will ignore the property if it's null or undefined.
  • PropertyNullability.PASS will pass the value as is.
import {DeserializeObject, JsonProperty, JsonType, PropertyNullability} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;

    @JsonProperty('a', JsonType.NUMBER, PropertyNullability.IGNORE)
    public age: number;
}

const jsonString = '{"fn": "John Doe"}';

const person = DeserializeObject(jsonString, Person);

console.log(person.name); // John Doe
console.log(person.age); // undefined

Default Values

If you want to set a default value, just set it in the initializer.

Setting a default value means you don't need to specify the type of the property; it will be inferred. (Unless you want to override it!)

Make sure to set the PropertyNullability to PropertyNullability.IGNORE so that the parser doesn't override it!

import {DeserializeObject, JsonProperty, JsonType, PropertyNullability} from "@unnoen/untypedjson";

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;

    @JsonProperty('a', JsonType.NUMBER, PropertyNullability.IGNORE)
    public age: number = 42;

    @JsonProperty('o')
    public occupation = 'Unemployed';
}

const jsonString = '{"fn": "John Doe", "o": "Software Engineer"}';

const person = DeserializeObject(jsonString, Person);

console.log(person.name); // John Doe
console.log(person.age); // 42
console.log(person.occupation); // Software Engineer

Custom Deserializers & Serializers

If you want to use a custom deserializer or serializer, just extend the JsonConverter class and override the Deserialize and Serialize methods.

Make sure you implement your own type checking!

import {DeserializeObject, JsonConverter, JsonProperty, JsonType} from "@unnoen/untypedjson";

class DateConverter extends JsonConverter<Date> {
    public Serialize(value: Date): string {
        return value.toISOString();
    }

    public Deserialize(value: JsonType.STRING): Date {
        return new Date(value);
    }
}

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;

    @JsonProperty('b', DateConverter)
    public birthday: Date;
}

const jsonString = '{"fn": "John Doe", "b": "1990-01-01"}';

const person = DeserializeObject(jsonString, Person);

console.log(person.name); // John Doe
console.log(person.birthday.getFullYear()); // 1990

Mixins

Ever wanted to add properties to a class from multiple other classes? Well now you can!

Use @JsonMixin to inherit properties from multiple classes. It also inherits the @JsonProperty decorators and getters/setters.

Make sure to define the interface for the class you're mixing in!

import {DeserializeObject, JsonMixin, JsonProperty, JsonType} from "@unnoen/untypedjson";

interface EmployeePerson extends Person, Employee {
} // You must define the interface otherwise the compiler will complain.

class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;
}

class Employee {
    @JsonProperty('s', JsonType.NUMBER)
    public salary: number;
}

@JsonMixin(Person, Employee)
class EmployeePerson {
    // This class will have the properties from Person and Employee
}

const jsonString = '{"fn": "John Doe", "s": 100000}';

const employee = DeserializeObject(jsonString, EmployeePerson);

console.log(employee.name); // John Doe
console.log(employee.salary); // 100000

Configuration

There are multiple ways to pass in options, you can do it on a per-class basis with the @JsonOptions decorator, or for the entire deserializer/serializer.

import {DeserializeObject, JsonOptions, JsonProperty, JsonType} from "@unnoen/untypedjson";

@JsonOptions({
    // Options go here...
    mapClassProperties: true,
})
class Person {
    @JsonProperty('fn', JsonType.STRING)
    public name: string;
}

const jsonString = '{"fn": "John Doe"}';

const person = DeserializeObject(jsonString, Person, {
    // ...or here.
    passUnknownProperties: true,
});

console.log(person.name); // John Doe

Options

OptionMethodTypeDefaultDescription
defaultNullabilityMode@JsonOptionsPropertyNullabilityPropertyNullability.MAPThe default nullability for all properties in the class.
mapClassProperties@JsonOptionsDeserializeOptionsbooleanfalseWhether to automatically map class properties to JSON properties if the names match.
passUnknownProperties@JsonOptionsDeserializeOptionsSerializeOptionsbooleanfalseWhether to pass unknown properties to the class.

Contributing

No pull request is too small!

If you want to contribute, just fork the repository and create a pull request into the main branch.

This project uses Yarn Berry for package management, however you can use npm if you want, it should work just fine.

Linting and tests are run on every commit, but don't worry if it fails! The most important part is contributing, linting issues and tests can be fixed later. :)

License

This project is licensed under the MIT License - see the LICENSE file for details

TL;DR - Do whatever you want with it. Just don't sue me if it breaks. I'd appreciate it if you could link back to this repository though. :)

Acknowledgements

  • json2typescript
    • UntypedJSON is heavily inspired by this project. I wanted a lightweight alternative and decided to make my own.
1.2.2

7 months ago

1.3.0

7 months ago

1.2.1

7 months ago

2.0.0-beta.0

6 months ago

1.2.0

11 months ago

1.1.0

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago