Skip to content

Commit

Permalink
Add maxDepth option to JsonthisOptions and ToJsonOptions to limit de…
Browse files Browse the repository at this point in the history
…pth of traversal (#6)

* Add maxDepth option to JsonthisOptions and ToJsonOptions to limit depth of traversal

* Add unit tests for maxDepth option

* Update README.md with new "Limit Serialization Depth" section
  • Loading branch information
davidecaroselli authored Apr 27, 2024
1 parent 395dc71 commit fcb0882
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 0 deletions.
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ for your data management needs. Explore the [Sequelize support](#sequelize-suppo
+ [Global Serializer](#global-serializer)
+ [Field-Specific Serializer](#field-specific-serializer)
* [Contextual Field-Specific Serializer](#contextual-field-specific-serializer)
* [Limit Serialization Depth](#limit-serialization-depth)
- [Circular References](#circular-references)
- [Sequelize support](#sequelize-support)
- [Let's Contribute Together!](#lets-contribute-together)
Expand Down Expand Up @@ -229,6 +230,40 @@ console.log(jsonthis.toJson(user, {context: {maskChar: "-"}}));
// { id: 1, email: '[email protected]' }
```

### Limit Serialization Depth

You can limit the depth of serialization by setting the `maxDepth` option at global level in `JsonthisOptions`
at construction time:

```typescript
class User {
id: number;
name: string;
friend?: User;

constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}

const user = new User(1, "John");
user.friend = new User(2, "Jane");
user.friend.friend = new User(3, "Bob");

const jsonthis = new Jsonthis({maxDepth: 1});
console.log(jsonthis.toJson(user));
// { id: 1, name: 'John', friend: { id: 2, name: 'Jane' } }
```

You can also set the `maxDepth` option at the method level in `ToJsonOptions`:

```typescript
const jsonthis = new Jsonthis();
console.log(jsonthis.toJson(user, {maxDepth: 1}));
// { id: 1, name: 'John', friend: { id: 2, name: 'Jane' } }
```

## Circular References

Jsonthis can detect circular references out of the box. When serializing an object with circular references, the default
Expand Down
55 changes: 55 additions & 0 deletions src/jsonthis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,5 +577,60 @@ describe("Jsonthis class", () => {
});
});
});

describe("with maxDepth option", () => {
class User {
id: number;
friend?: User;

constructor(id: number, friend?: User) {
this.id = id;
if (friend) this.friend = friend;
}
}

const user = new User(1, new User(2, new User(3, new User(4))));

it("should serialize to unlimited depth by default", () => {
const jsonthis = new Jsonthis();
expect(jsonthis.toJson(user)).toStrictEqual({
id: 1,
friend: {
id: 2,
friend: {
id: 3,
friend: {id: 4}
}
}
});
});

it("should stop serialization to global maxDepth", () => {
const jsonthis = new Jsonthis({maxDepth: 2});
expect(jsonthis.toJson(user)).toStrictEqual({
id: 1,
friend: {
id: 2,
friend: {id: 3}
}
});
});

it("should stop serialization to field's maxDepth", () => {
const jsonthis = new Jsonthis();
expect(jsonthis.toJson(user, {maxDepth: 1})).toStrictEqual({
id: 1,
friend: {id: 2}
});
});

it("should stop serialization to field's maxDepth over global maxDepth", () => {
const jsonthis = new Jsonthis({maxDepth: 2});
expect(jsonthis.toJson(user, {maxDepth: 1})).toStrictEqual({
id: 1,
friend: {id: 2}
});
});
});
});
});
6 changes: 6 additions & 0 deletions src/jsonthis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@ export type JsonthisOptions = {
case?: "camel" | "snake" | "pascal"; // The case to use for field names, default is to keep field name as is.
sequelize?: Sequelize; // Install Jsonthis to this Sequelize instance.
circularReferenceSerializer?: JsonTraversalFn<any>; // The custom serializer function for circular references, default it to throw an error.
maxDepth?: number; // The maximum depth to traverse the object, default is unlimited.
}

/**
* Options for the toJson() method.
*/
export type ToJsonOptions = {
context?: any; // The user-defined context object to pass to the serializers.
maxDepth?: number; // The maximum depth to traverse the object, default is the global maxDepth in JsonthisOptions.
}

export class CircularReferenceError extends Error {
Expand Down Expand Up @@ -149,6 +151,10 @@ export class Jsonthis {
try {
state.visited.dive();

const maxDepth = options?.maxDepth || this.options.maxDepth || Infinity;
if (state.visited.depth > maxDepth)
return undefined;

// Check for circular references
if (state.visited.visit(value)) {
if (this.options.circularReferenceSerializer)
Expand Down
4 changes: 4 additions & 0 deletions src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ export class VisitMap {
private depths: Array<Map<any, Array<any>>> = [new Map<any, Array<any>>()]
private currentDepth: number = 0

public get depth(): number {
return this.currentDepth;
}

private has(value: any): boolean {
for (const map of this.depths) {
const values = map.get(value);
Expand Down

0 comments on commit fcb0882

Please sign in to comment.