GithubHelp home page GithubHelp logo

olosegres / jsona Goto Github PK

View Code? Open in Web Editor NEW
203.0 7.0 27.0 230 KB

Tiny and simple JSON:API serializer / deserializer. Creates simplified objects from JSON or stored reduxObject, creates JSON from the same simplified objects (in according with JSON API specification)

License: MIT License

TypeScript 100.00%
json-api dataformat converter normalization denormalization serialization jsonapi json

jsona's Introduction

Jsona

JSON API v1.0 specification serializer and deserializer for use on the server and in the browser.

  • From JSON to simplified objects
  • Back from simplified objects to JSON (in according with json:api specification)
  • Also from "reduxObject" to simplified objects (reduxObject is a result object of json-api-normalizer)

NPM dependencies downloads

What problem does it solve?

When you work with API standardized to json:api specification, you're dealing with a special and optimized JSON data format in the request and response body. You can get data of several entities that are related to each other, but you'll receive it in array (included). You may need to send modified back to server (or new data) in accordance with specification.

This may puzzle you with the following questions:

  • How to get necessary entity from included array many times more inconvenient and optimal?
  • How to describe data from server, working with typings (TypeScript, Flow)?
  • How to send JSON to the server without manually assembling JSON in accordance with specification?

Installation

npm i jsona --save

or

yarn add jsona

How to use

You need to instantiate Jsona once, then use its public methods to convert data.

import Jsona from 'jsona';
const dataFormatter = new Jsona();

deserialize - creates simplified object(s) from json

const json = {
    data: {
          type: 'town',
          id: '123',
          attributes: {
              name: 'Barcelona'
          },
          relationships: {
              country: {
                  data: {
                      type: 'country',
                      id: '32'
                  }
              }
          }
    },
    included: [{
        type: 'country',
        id: '32',
        attributes: {
            name: 'Spain'
        }
    }]
};

const town = dataFormatter.deserialize(json);
console.log(town); // will output:
/* {
    type: 'town',
    id: '123',
    name: 'Barcelona',
    country: {
        type: 'country',
        id: '32',
        name: 'Spain'
    },
    relationshipNames: ['country']
} */

serialize - creates json from simplified object(s)

const user = {
    type: 'user',
    id: 1,
    categories: [{ type: 'category', id: '1', name: 'First category' }],
    town: {
        type: 'town',
        id: '123',
        name: 'Barcelona',
        country: {
            type: 'country',
            id: '32',
            name: 'Spain'
        },
        relationshipNames: ['country']
    },
    relationshipNames: ['categories', 'town']
};

const newJson = dataFormatter.serialize({
    stuff: user, // can handle array
    includeNames: ['categories', 'town.country'] // can include deep relations via dot
});

console.log(newJson); // will output:
/* {
    data: {
        type: 'user',
        id: 1,
        relationships: {
            categories: {
                data: [{ type: 'category', id: '1' }]
            },
            town: {
                data: { type: 'town', id: '123' }
            }
        }
    },
    included: [{
        type: 'category',
        id: '1',
        attributes: {
            name: 'First category',
        }
    }, {
        type: 'town',
        id: '123',
        attributes: {
             name: 'Barcelona',
        },
        relationships: {
             country: {
                 data: {
                     type: 'country',
                     id: '32',
                 }
             }
        }
    }, {
        type: 'country',
        id: '32',
        attributes: {
            name: 'Spain',
        }
    }]
}*/

denormalizeReduxObject - creates simplified object(s) from reduxObject

"reduxObject" - result object of json-api-normalizer

const reduxObject = reduxStore.entities; // depends on where you store it
const town = dataFormatter.denormalizeReduxObject({reduxObject, entityType: 'town', entityIds: '123'});
console.log(town); // if there is such town and country in reduxObject, it will output:
/* {
    type: 'town',
    id: '123',
    name: 'Barcelona',
    country: {
        type: 'country',
        id: '34',
        name: 'Spain'
    },
    relationshipNames: ['country']
} */

Customize

Build process and property names

You can control process of building simplified objects, just use your own propertyMappers when Jsona instantiates.

With IJsonPropertiesMapper you can implement your way of creation simplified objects (data models) from JSON, with IModelPropertiesMapper implement how to give back values from data model to JSON.

It gives unlimited possibilities to integrate Jsona with react, redux, angular2

Example of passing your own propertyMappers to Jsona:

import Jsona from 'jsona';
import {MyModelPropertiesMapper, MyJsonPropertiesMapper} from 'myPropertyMappers';

export const dataFormatter = new Jsona({
    modelPropertiesMapper: MyModelPropertiesMapper,
    jsonPropertiesMapper: MyJsonPropertiesMapper
});

Also, there is built-in switchCasePropertyMappers, that you can use if need to automatically transform property names from kebab, snake, camel case and back.

Cache

For faster creation of simplified objects from json, it uses a cache for already processed json-entity, see DeserializeCache that uses by default. It possible to provide your own IDeserializeCache manager:

import Jsona from 'jsona';
import {MyOwnDeserializeCache} from './index';

export const dataFormatter = new Jsona({
    DeserializeCache: MyOwnDeserializeCache
});

License

Jsona, examples provided in this repository and in the documentation are MIT licensed.

jsona's People

Contributors

apsavin avatar artem-galas avatar blvdmitry avatar cobypear avatar dependabot[bot] avatar dlq84 avatar dpikt avatar erikdubbelboer avatar jiahuanwu avatar jordandukart avatar masa-shin avatar mherrerarendon avatar olosegres avatar penghouho avatar rintaun avatar ryosukehomma avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

jsona's Issues

Circular structure jsonapi breaks deserializer

The following JSONAPI response will break the deserializer

{
  data: {
    id: '1',
    type: 'test',
    relationships:{
      questions: {
        data: [{ id: '1', type: 'result' }]
      },
      answers: {
        data: [{ id: '1', type: 'answer' }]
      }
    }
  }, 
  included :[
    {
      id: '1',
      type: 'question',
      attributes: {
        one: '1',
        two: '2'
      },
      relationships:{
        test :{
          data: {id: '1', type: 'answer'}
        }
      }
    },
    {
      id: '1',
      type: 'answer',
      attributes: {
        three: '3',
        four: '4'
      },
      relationships:{
        test :{
          data: {id: '1', type: 'question'}
        }
      }
    },
  ]
}

Error is

TypeError: Converting circular structure to JSON
        --> starting at object with constructor 'Object'
        |     property 'test' -> object with constructor 'Object'
        --- property 'test' closes the circle
        at stringify (<anonymous>)

It's bsaiclly trying to do test.question.answer.question.answer.... i am guessing.

Perhaps have a limit to the depth of an option for depth to stop the circular reference.

Switching nested attribute object keys to camel case is not working

Hello.

When I tried to switch the following json to camel case, the change to camel case for nested keys was not done.

{
  "data": [
    {
      "attributes": {
        "reportable": {
          "latest_image_urls": {
            "large": "test.jpg"
          }
        }
      }
    }
  ]
}

I read the json api spec and it says "Attributes may contain any valid JSON value".
Can you please fix this so that nested attributes are converted to camel cases according to the specification?

This is my first time creating an OSS issue, so if there is any information missing, please point it out.

Thank you.

Error during deserialization

Since version .11 there is a change that causes error for me:

    buildModelByData(data: TJsonApiData): TJsonaModel {
        const entityKey = createEntityKey(data);

        let model;

        const onlyTypeIdInData = Object.keys(data).length === 2 && data.type && data.id;

        if (entityKey && onlyTypeIdInData) {

error message:
RangeError: Maximum call stack size exceeded

I had to remove the changes to:


    buildModelByData(data: TJsonApiData): TJsonaModel {
        const entityKey = createEntityKey(data);

        let model;

        if (entityKey) {

I thing I have normal JSON:API data, but with a lot of includes and relations. So why there is this change?

Deserializing Relationships With Embedded Data

I want to deserialize a relationship with embedded data. Do you have a recommended way of handling that within jsona?

Example:

{
    links: {},
    data: {
      type: 'characters',
      id: '1234',
      attributes: {
        name: 'Mickey'
      },
      relationships: {
        skills: {
          data: [{
            type: 'skills',
            id: 'swords',
            attributes: {
              level: 20
            }
          }, {
            type: 'skills',
            id: 'bows',
            attributes: {
              level: 3
            }
          }]
        }
      }
    }
  }

Deserializes to:

{ type: 'characters',
      id: '1234',
      name: 'Mickey',
      skills: 
       [ { type: 'skills', id: 'swords' },
         { type: 'skills', id: 'bows' } ],
      relationshipNames: [ 'skills' ] }

What I'm looking to for:

{ type: 'characters',
      id: '1234',
      name: 'Mickey',
      skills: 
       [ { type: 'skills', id: 'swords', level: 20 },
         { type: 'skills', id: 'bows', level: 3 } ],
      relationshipNames: [ 'skills' ] }

Bug in deserialize: "included resources" data takes priority over "primary data"

Hello,

I have found a bug in the deserialization where the "included resource" is kept, instead of the "primary data" for the same resource.

  • my payload is made of primary data of type box AND processes
{ 
  data: [ 
    { type: "box" }, 
    { type: "process" } 
  ]
}
  • I have the following relationships : box -> revision -> link -> process
  • my "box" in the primary data related to the process in the primary data

Now let's evolve our example (see the 2 "watch out" comments)

{
  "data": [
    {
      "id": "1",
      "type": "box",
      "relationships": {
        "revision": {
          "data": {
            "id": "1",
            "type": "revision"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "process",
      "attributes": {
        "prop-1": "hello" // watch out : primary data has "prop-1"
      }
    }
  ],
  "included": [
    {
      "id": "1",
      "type": "revision",
      "relationships": {
        "link": {
          "data": {
            "id": "1",
            "type": "link"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "link",
      "relationships": {
        "process": {
          "data": {
            "id": "1",
            "type": "process"
          }
        }
      }
    },
    {
      "id": "1",
      "type": "process",
      "attributes": {
        "prop-2": "prop2" // watch out, related data has prop-2 !
      }
    }
  ]
}

you can try it there : https://codesandbox.io/s/jsona-bug-deserialize-iq4owb

On deserialize i'm expecting the final object to have prop-1 and prop-2 to be ignored.

In this scenario above, the deserialization will retain the process object with "prop-2", i.e. the "included resource" and not the primary data object (with prop-1)

Screenshot 2022-07-06 at 14 34 19

The bug only starts to happen when there is 2 level of relationships in the related data ( for example if i had box -> revision -> process that would be fine) which is why i think it's a bug !

Infinately nested relationships

Not sure if this is an issue or not. An example of what I'm talking about: https://d.pr/v/NEVuzG

The problem here is a shipment has a relationship to loadData and loadData has a relationship to the same shipment causing the output of...

dataFormatter.denormalizeReduxObject({
      reduxObject: state.data,
      entityType: "shipment"
})

...to be what's shown in the link. Is this a concern?

denormalizeReduxObject isn't getting stuck in an infinite loop traversing and building the relationships, so what's going on here? Is it just referencing the same object? Also what implications does this have with react's virtual dom reconciliation? redux-object's build method uses getters for scenarios like this, but it doesn't do as good of a job of building those relationships.

switchCasePropertyMappers from Javascript

I noticed that there's a switchCasePropertyMappers.ts which seems to provide a way to camelize properties. However, that file only exists in src, and there is no compiled lib/switchCasePropertyMappers.js. Was this an oversight, or can I only use that if I'm using typescript?

Browsers support

Hi, @olosegres! Thanks for awesome project! it's really useful. What do you think about browsers support? It would be nice if jsona had a bundle for browser that we can directly use it like:

<script src"./dist/path/to/bundle.js"></script>

Cannot find module 'Jsona

github build issue:
Error: src/app/node/case/case.component.ts:2:19 - error TS2307: Cannot find module 'Jsona' or its corresponding type declarations.

In the angular component:
import Jsona from 'Jsona';

in local dev is ok, but github build have issue.

Keep relationship links when deserializing

Hi, like the title suggests I was wondering if there was any way to keep the relationship links after deserializing data. My use case is rather simple I would say, as I need deserialization in almost every part of my app, the easiest way for me was implementing response interceptor on axios instance, and getting deserialized data on every request. However, there are few parts where I want to be able to load relationships separately on user request and those links come in handy. I've already kinda solved the issue by making the interceptor optional, and when I do need those links, I disable it and deserialize after getting the links, but I'm not really happy with that approach. I could also append those links to deserialize data inside the interceptor but I'm not completely sure how that would look like on a collection of resources, so just wanted to make sure whatever or not this was possible with jsona perhaps. Any help would be appreciated.

Throws `undefined` error when relationships are not included

It's legal in JSON-API to exclude relationships. In fact, relationships should only be included when explicitly requested using the include= querystring.

However, jsona throws an undefined error when a certain relationship is not included.

switchChar meta to camelCased

Hi, it would be great if switchChar can convert meta to camelCased.

const products = new Jsona({ modelPropertiesMapper: new SwitchCaseModelMapper(), jsonPropertiesMapper: new SwitchCaseJsonMapper({ switchChar: '_' }), }).deserialize(data);

ะกะฝะธะผะพะบ ัะบั€ะฐะฝะฐ 2019-05-14 ะฒ 16 45 24

RangeError: Maximum call stack size exceeded

Hi!

const json = require('./test.json')
const Jsona = require('Jsona').default

const dataFormatter = new Jsona()

console.log(JSON.stringify(dataFormatter.deserialize(json)))

test.json.txt

I'm getting error when my api service respond to me this json that includes object with block_translations, if I remove it from json all is works fine. Where is error ? In test.json or?

No way to build a json api structure with nested relationships?

jsona.serialize({ stuff: { 
  id: '1', 
  type: 'firstEntity', 
  relationshipNames: ['secondEntity'], 
  secondEntity: {
    id: '2', 
    type: 'secondEntity', 
    bar: 'baz', 
    thirdEntities: [{
      id: '3', 
      type: 'thirdEntity', 
      foo: 'bar',
    }], 
    relationshipNames: ['thirdEntities']}
  }, 
  includeNames: ['secondEntity'] 
})

Current output has included with secondEntity entity only.
It seems that it's impossible to build json structure with thirdEntity entity in the included section.
Am I right?
Is it possible to use some workaround?

Source map issue?

Hello,

I am getting several similar issues :
WARNING in ./node_modules/jsona/lib/Jsona.js Module Warning (from ./node_modules/source-map-loader/dist/cjs.js): Failed to parse source map from './node_modules/jsona/src/Jsona.ts' file: Error: ENOENT: no such file or directory, open './node_modules/jsona/src/Jsona.ts'

The npm package is missing the src folder?

Edit: this message comes from webpack when running CRA build script (facebook/create-react-app#11767) and might indicate a source map misconfiguration in this package.

Error when using Jsona on Nuxt server side

When importing Jsona as suggested in the readme:

import Jsona from 'jsona'

It is resulting in a Jsona is not a constructor error when using by a Nuxt application on the server-side.

I have found that using the named import works

import { Jsona } from 'jsona'

Jsona version 1.12.1

Custom ModelsSerializer

First of all, thank you for this wonderful library. It allows creating a simple layer between reduxObject and the views. Currently, I have a specific use case for building the JSON and need some guidance.

Property mappers give the flexibility to structure the data for the serializer, but is there a way to change how to relationships will be built in JSON?

Specifically, I want to preserve attributes in a relationship model, but only id and type is parsed.
I don't want to include that data in the included array, but pass it inline.

Is there a way to tackle this with jsona?

`meta` property in relationship is dropped when deserializing

According to the spec, a relationship's data can contain meta, i.e. jsonApiObject.data[*].relationships.<field>.data[*].meta can exists. (Also applies to singular data/response object)

Or more formally, according to the specification:

  1. Top Level contains data, which must be either single or array of "resource objects"
  2. Resource Objects may contain relationships, which is a "relationships object"
  3. (Each) Relationship Object contains data, which is a "resource linkage"
  4. Resource Linkage can be either single or array of "resource identifier object"
  5. Resource Identifier Objects may include meta

In my case which uses Jsona with Drupal, problem arises because Drupal uses individual relationship object's meta to transmit image alt texts. As a result the alt text is lost.

As an example, this is a stripped JSON:API response from Drupal:

{
  "jsonapi": {
    "version": "1.0",
    "meta": {
      "links": {
        "self": {
          "href": "http://jsonapi.org/format/1.0/"
        }
      }
    }
  },
  "data": [{
    "type": "node--site_configuration",
    "id": "f8895943-7f51-451b-bb8f-a479853f1b4b",
    "links": {
      "self": {
        "href": "http://acmecorp-cms.lndo.site/en/jsonapi/node/site_configuration/f8895943-7f51-451b-bb8f-a479853f1b4b?resourceVersion=id%3A230"
      }
    },
    "attributes": {
      "langcode": "en",
      "title": "Site Configuration"
    },
    "relationships": {
      "field_logo": {
        "data": {
          "type": "file--file",
          "id": "551ec1b9-b0c6-4649-bb7c-b6ebb09354ff",
          "meta": {
            "alt": "ACME Corp Logo",
            "title": "",
            "width": 206,
            "height": 278
          }
        },
        "links": {
          "related": {
            "href": "http://acmecorp-cms.lndo.site/en/jsonapi/node/site_configuration/f8895943-7f51-451b-bb8f-a479853f1b4b/field_logo?resourceVersion=id%3A230"
          },
          "self": {
            "href": "http://acmecorp-cms.lndo.site/en/jsonapi/node/site_configuration/f8895943-7f51-451b-bb8f-a479853f1b4b/relationships/field_logo?resourceVersion=id%3A230"
          }
        }
      }
    }
  }],
  "included": [{
    "type": "file--file",
    "id": "551ec1b9-b0c6-4649-bb7c-b6ebb09354ff",
    "links": {
      "self": {
        "href": "http://acmecorp-cms.lndo.site/en/jsonapi/file/file/551ec1b9-b0c6-4649-bb7c-b6ebb09354ff"
      }
    },
    "attributes": {
      "langcode": "en",
      "uri": {
        "value": "public://2020-07/acmecorp-logo-colour-2x.png",
        "url": "http://acmecorp.oss-cn-hongkong.aliyuncs.com/s3fs-public/2020-07/acmecorp-logo-colour-2x.png"
      },
      "filemime": "image/png",
      "filesize": 54952
    }
  }]
}

Notice there is alt: "ACME Corp Logo" in meta object in data[0].relationships.field_logo.data.

If we try to parse it with a simple Jsona code, i.e.

const Jsona = require('jsona').default;

const dataFormatter = new Jsona();

const data = require('./drupal-jsonapi-response.json');

console.log(JSON.stringify(dataFormatter.deserialize(data), null, 2));

The output is

[
  {
    "type": "node--site_configuration",
    "id": "f8895943-7f51-451b-bb8f-a479853f1b4b",
    "langcode": "en",
    "title": "Site Configuration",
    "links": {
      "self": {
        "href": "http://acmecorp-cms.lndo.site/en/jsonapi/node/site_configuration/f8895943-7f51-451b-bb8f-a479853f1b4b?resourceVersion=id%3A230"
      }
    },
    "field_logo": {
      "type": "file--file",
      "id": "551ec1b9-b0c6-4649-bb7c-b6ebb09354ff",
      "langcode": "en",
      "uri": {
        "value": "public://2020-07/acmecorp-logo-colour-2x.png",
        "url": "http://acmecorp.oss-cn-hongkong.aliyuncs.com/s3fs-public/2020-07/acmecorp-logo-colour-2x.png"
      },
      "filemime": "image/png",
      "filesize": 54952,
      "links": {
        "self": {
          "href": "http://acmecorp-cms.lndo.site/en/jsonapi/file/file/551ec1b9-b0c6-4649-bb7c-b6ebb09354ff"
        }
      }
    },
    "relationshipNames": [
      "field_logo"
    ]
  }
]

As seen, the meta object along with alt: "ACME Corp Logo" is gone.

Links are ignored during deserialization

the default simplePropertyMappers does not work with pagination when body contains link

example..

{
  "meta": {
    "totalPages": 13
  },
  "data": [
    {
      "type": "articles",
      "id": "3",
      "attributes": {
        "title": "JSON:API paints my bikeshed!",
        "body": "The shortest article. Ever.",
        "created": "2015-05-22T14:56:29.000Z",
        "updated": "2015-05-22T14:56:28.000Z"
      }
    }
  ],
  "links": {
    "self": "http://example.com/articles?page[number]=3&page[size]=1",
    "first": "http://example.com/articles?page[number]=1&page[size]=1",
    "prev": "http://example.com/articles?page[number]=2&page[size]=1",
    "next": "http://example.com/articles?page[number]=4&page[size]=1",
    "last": "http://example.com/articles?page[number]=13&page[size]=1"
  }
}

when deserialized, will be

[ { type: 'articles',
    id: '3',
    title: 'JSON:API paints my bikeshed!',
    body: 'The shortest article. Ever.',
    created: '2015-05-22T14:56:29.000Z',
    updated: '2015-05-22T14:56:28.000Z' } ]

I see that setLinks exists on the simplePropertyMapper here

but it seems that only links inside data will be checked, but not the body.links ( which is where links will exist if it's for pagination )

case change of property names

Am I missing a way of changing property names from kebab-case to camelCase and back again when serializing and deserializing?

Deserialize `snake_case` to `camelCase`

Using jsona for speedy deserializing of our jsonapi! And was particularly interested in this related issue that was resolved: #1

Issue

I want to deserialize snake_case property names to camelCase

However it seems that jsona currently only handles jsonapi's kebab-case names but from what I understand of the jsonapi spec (https://jsonapi.org/format/#document-member-names) member names are allowed to be snake_case:

Additionally, the following characters are allowed in member names, except as the first or last character:

  • U+002D HYPHEN-MINUS, โ€œ-โ€œ
  • U+005F LOW LINE, โ€œ_โ€
  • U+0020 SPACE, โ€œ โ€œ (not recommended, not URL safe)

The API I am working with has all of its attributes in snake_case.

What I find strange is that this behaviour in jsona is intentional, as it is verified that snake_case does not become camelCase via test coverage here:

expect(model['foo_bar']).to.be.equal(5);
Wondering what the reason is for this?

Possible Solution:

By changing this line here:

const camelizedType = type.replace(/-([a-z0-9])/g, function (g) { return g[1].toUpperCase(); });

to something like:

const camelizedType = type.replace(/(-|_)([a-z0-9])/g, function (g) { return g[1].toUpperCase(); });

handles the snake_case member names!

Demo

I have put together a tiny demo of it here that console.logs the result of the deserializing:

https://codesandbox.io/s/ppy60ykz3j?fontsize=14

Added a bit of a post-processing to do the modification myself to get everything to camelCase but I feel it's not optimal that I have to do this.

Object with key 'type'

Hi!
What if object itselfs have field with key type?
How to read/write to it?

For example:

{
  "data": {
    "type": "schedules",
    "attributes": {
      "type": "ON",
      "day": "DAY",
      "date": "2019-01-16",
      "time": "13:15"
    },
    "relationships": {
      "webinar": {
        "data": {
          "id": "78bf418f-3f6c-4cbe-a191-635880f6f049",
          "type": "webinar"
        }
      }
    }
  },
  "included": [{
    "id": "78bf418f-3f6c-4cbe-a191-635880f6f049",
    "type": "webinar",
    "attributes": {}
  }]
}

jsona.serialize requires an obligatory includeNames

Jsona.serialize requires an obligatory parameter - includeNames, although it should be optional.
I saw the test cases and I can't understand how they can work successfully.
(Test case)
Here is the code below which causes this issue:

import Jsona from 'jsona/lib/Jsona';
const jsona = new Jsona();
const sessionData = {
      type: 'sessions',
      name: this.name,
      start_date_time: this.start_date_time
    };
const sessionDataSerialize = jsona.serialize({stuff: sessionData});
TS2345:Argument of type '{ stuff: { type: string; name: string; start_date_time: Date; }; }' is not assignable to parameter of type '{ stuff: TJsonaModel | TJsonaModel[]; includeNames: string[] | TJsonaNormalizedIncludeNamesTree; }'.
  Property 'includeNames' is missing in type '{ stuff: { type: string; name: string; start_date_time: Date; }; }'.

When serializing with includeNames, only the first element of array is included in the resulting body if elements don't have id

Awesome job with this library!

As the title states, only the first element of the array is included when the elements don't have an id. I would expect all elements to be included.

According to the JSONAPI spec, included resources do not need to contain an id field. See here for more information.

I created a unit test that showcases the issue. Feel free to clone and run the test.

TS Types: Using `super` in custom model properties mapper requires type casting

I am a TypeScript noob, so excuse me if I am doing things totally wrong here.

I would like to use the modelPropertiesMapper option to implement custom format for "special" attribute. I only want to change that one "special" field and don't want to think about other things so I am exteding the ModelPropertiesMapper in and would like to use super. Then I see this error:

screen shot 2018-11-15 at 23 42 14

Of course this just works if I use type casting, but It feels wrong:

screen shot 2018-11-15 at 23 42 29

I guess this is because when using the npm package the generated types tell me that getAttributes always return {}, which is not helpful.

Suggestion: Can we change the return type of getAttributes to any or TAnyKeyValueObject?

Wrong result when entity has null relationship and is not the first one in array

"jsona": "^1.1.10"


deserialize({
  "data": [
    {
      "type": "category",
      "id": "3",
      "attributes": {
        "slug": "ya"
      },
      "relationships": {
        "parent": {
          "data": {
            "type": "category",
            "id": "0"
          }
        }
      }
    },
    {
      "type": "category",
      "id": "0",
      "attributes": {
        "slug": "home"
      },
      "relationships": {
        "parent": {
          "data": null
        }
      }
    }
  ]
})

output

[
    {
        "type": "category", 
        "id": "3", 
        "slug": "ya", 
        "parent": {
            "type": "category", 
            "id": "0"
        }, 
        "relationshipNames": [
            "parent"
        ]
    }, 
    {
        "type": "category", 
        "id": "0"
    }
]

However, when it is the first element in the array,

deserialize({
    "data": [
        {
            "type": "category", 
            "id": "0", 
            "attributes": {
                "slug": "home"
            }, 
            "relationships": {
                "parent": {
                    "data": null
                }
            }
        }
    ]
})

it works good.

[
    {
        "type": "category", 
        "id": "0", 
        "slug": "home"
    }
]

In addition, I think it would be better to keep the null value, like this:

[
    {
        "type": "category", 
        "id": "0", 
        "slug": "home",
        "parent": null, 
        "relationshipNames": [
            "parent"
        ]
    }
]

Nested json api object is not properly deserilized

Example:

{
   "data":{
      "type":"event",
      "id":"evt_9pjxVWctAtwYo3EsjfeuBjXT",
      "attributes":{
         "name":"inquiry.transitioned",
         "payload":{
            "data":{
               "type":"inquiry",
               "id":"inq_ZWRL8Dr2KjhHcewoj3meoDU3",
               "attributes":{
                  "status":"created",
                  "reference-id":"clblepigj003ixrii2r1i03li_us",
                  "note":null,
                  "behaviors":{
                     "request-spoof-attempts":null,
                     "user-agent-spoof-attempts":null,
                     "distraction-events":null,
                     "hesitation-baseline":null,
                     "hesitation-count":null,
                     "hesitation-time":null,
                     "shortcut-copies":null,
                     "shortcut-pastes":null,
                     "autofill-cancels":null,
                     "autofill-starts":null,
                     "devtools-open":null,
                     "completion-time":null,
                     "hesitation-percentage":null,
                     "behavior-threat-level":null
                  },
                  "tags":[
                     
                  ],
                  "creator":"[email protected]",
                  "reviewer-comment":null,
                  "created-at":"2023-02-13T23:28:43.000Z",
                  "started-at":null,
                  "completed-at":null,
                  "failed-at":null,
                  "marked-for-review-at":null,
                  "decisioned-at":null,
                  "expired-at":null,
                  "redacted-at":null,
                  "previous-step-name":"ui_step",
                  "next-step-name":"ui_step_2",
                  "name-first":"John",
                  "name-middle":null,
                  "name-last":"Doe",
                  "birthdate":"1985-10-27",
                  "address-street-1":null,
                  "address-street-2":null,
                  "address-city":null,
                  "address-subdivision":null,
                  "address-subdivision-abbr":null,
                  "address-postal-code":null,
                  "address-postal-code-abbr":null,
                  "social-security-number":null,
                  "identification-number":null,
                  "email-address":null,
                  "phone-number":null,
                  "fields":{
                     "birthdate":{
                        "type":"date",
                        "value":"1985-10-22"
                     },
                     "name-last":{
                        "type":"string",
                        "value":"Doe"
                     },
                     "name-first":{
                        "type":"string",
                        "value":"John"
                     },
                     "name-middle":{
                        "type":"string",
                        "value":null
                     },
                     "address-city":{
                        "type":"string",
                        "value":null
                     },
                     "phone-number":{
                        "type":"string",
                        "value":null
                     },
                     "email-address":{
                        "type":"string",
                        "value":null
                     },
                     "current-selfie":{
                        "type":"selfie",
                        "value":null
                     },
                     "address-street-1":{
                        "type":"string",
                        "value":null
                     },
                     "address-street-2":{
                        "type":"string",
                        "value":null
                     },
                     "selected-id-class":{
                        "type":"string",
                        "value":null
                     },
                     "address-postal-code":{
                        "type":"string",
                        "value":null
                     },
                     "address-subdivision":{
                        "type":"string",
                        "value":null
                     },
                     "address-country-code":{
                        "type":"string",
                        "value":null
                     },
                     "identification-class":{
                        "type":"string",
                        "value":null
                     },
                     "current-government-id":{
                        "type":"government_id",
                        "value":null
                     },
                     "identification-number":{
                        "type":"string",
                        "value":null
                     },
                     "selected-country-code":{
                        "type":"string",
                        "value":"US"
                     },
                     "social-security-number":{
                        "type":"string",
                        "value":null
                     }
                  }
               },
               "relationships":{
                  "account":{
                     "data":{
                        "type":"account",
                        "id":"act_sT7d9rV6cpedbay3KqFE8XyQ"
                     }
                  },
                  "template":{
                     "data":null
                  },
                  "inquiry-template":{
                     "data":{
                        "type":"inquiry-template",
                        "id":"itmpl_Y4TP7U7gX9Uyd8ocVtTBhe5a"
                     }
                  },
                  "inquiry-template-version":{
                     "data":{
                        "type":"inquiry-template-version",
                        "id":"itmplv_LiqByoYHERRpZQ6YmcxanZe5"
                     }
                  },
                  "reviewer":{
                     "data":null
                  },
                  "reports":{
                     "data":[
                        
                     ]
                  },
                  "verifications":{
                     "data":[
                        
                     ]
                  },
                  "sessions":{
                     "data":[
                        {
                           "type":"inquiry-session",
                           "id":"iqse_WVV6Jyt3Xj8pfZ2dReAaPM7e"
                        },
                        {
                           "type":"inquiry-session",
                           "id":"iqse_XJKSyAFd9ZuEzNXbPTSR5697"
                        }
                     ]
                  },
                  "documents":{
                     "data":[
                        
                     ]
                  },
                  "selfies":{
                     "data":[
                        
                     ]
                  }
               }
            },
            "included":[
               {
                  "type":"account",
                  "id":"act_sT7d9rV6cpedbay3KqFE8XyQ",
                  "attributes":{
                     "reference-id":"clblepigj003ixrii2r1i03li_us",
                     "created-at":"2023-02-13T22:38:59.000Z",
                     "updated-at":"2023-02-13T23:06:57.000Z",
                     "redacted-at":null,
                     "name-first":"John",
                     "name-middle":null,
                     "name-last":"Doe",
                     "phone-number":null,
                     "email-address":null,
                     "address-street-1":"600 CALIFORNIA STREET",
                     "address-street-2":"609",
                     "address-city":"SAN FRANCISCO",
                     "address-subdivision":"CA",
                     "address-postal-code":"94109",
                     "country-code":"US",
                     "birthdate":"1985-10-21",
                     "social-security-number":null,
                     "tags":[
                        
                     ],
                     "identification-numbers":{
                        "":[
                           {
                              "issuing-country":null,
                              "identification-class":null,
                              "identification-number":"680-47-4980",
                              "created-at":"2023-02-13T22:39:48.666Z",
                              "updated-at":"2023-02-13T22:39:48.666Z"
                           },
                           {
                              "issuing-country":null,
                              "identification-class":null,
                              "identification-number":"680-48-4970",
                              "created-at":"2023-02-13T22:44:15.979Z",
                              "updated-at":"2023-02-13T22:44:15.979Z"
                           }
                        ],
                        "dl":[
                           {
                              "issuing-country":"US",
                              "identification-class":"dl",
                              "identification-number":"I1234562",
                              "created-at":"2023-02-13T23:03:25.496Z",
                              "updated-at":"2023-02-13T23:03:25.496Z"
                           }
                        ]
                     }
                  }
               },
               {
                  "type":"inquiry-template",
                  "id":"itmpl_Y4TP7U7gX9Uyd8ocVtTBhe5a",
                  "attributes":{
                     "status":"active",
                     "name":"Government ID and Selfie"
                  }
               },
               {
                  "type":"inquiry-template-version",
                  "id":"itmplv_LiqByoYHERRpZQ6YmcxanZe5",
                  "attributes":{
                     "name-display":"",
                     "enabled-locales":[
                        "ar-EG",
                        "bn",
                        "de",
                        "en-US",
                        "es-MX",
                        "fr",
                        "he",
                        "hi",
                        "hu",
                        "id-ID",
                        "it",
                        "ja",
                        "ko-KR",
                        "lt",
                        "ms",
                        "nl-NL",
                        "pl",
                        "pt-BR",
                        "ro",
                        "ru",
                        "sv",
                        "ta",
                        "th",
                        "tr-TR",
                        "uk-UA",
                        "vi",
                        "zh-CN",
                        "zh-TW"
                     ],
                     "theme":{
                        "border-radius":null,
                        "border-radius-modal":null,
                        "border-width":null,
                        "button-background-image":null,
                        "button-font-weight":null,
                        "button-position":null,
                        "button-shadow-strength":null,
                        "button-text-transform":null,
                        "color-button-primary":null,
                        "color-button-secondary":null,
                        "color-button-secondary-fill":null,
                        "color-button-primary-fill-disabled":null,
                        "color-button-secondary-fill-disabled":null,
                        "color-error":null,
                        "color-font":null,
                        "color-font-button-primary":null,
                        "color-font-button-secondary":null,
                        "color-font-small":null,
                        "color-font-title":null,
                        "color-icon-header":null,
                        "color-input-background":null,
                        "color-input-border":null,
                        "color-link":null,
                        "color-modal-background":null,
                        "color-primary":null,
                        "color-progress-bar":null,
                        "color-success":null,
                        "color-warning":null,
                        "color-divider":null,
                        "color-dropdown-background":null,
                        "color-dropdown-option":null,
                        "font-family":null,
                        "font-family-title":null,
                        "font-url":null,
                        "font-size-body":null,
                        "font-size-header":null,
                        "font-size-small":null,
                        "line-height-body":null,
                        "line-height-header":null,
                        "line-height-small":null,
                        "header-font-weight":null,
                        "header-margin-bottom":null,
                        "icon-color-primary":null,
                        "icon-color-highlight":null,
                        "icon-color-stroke":null,
                        "icon-color-background":null,
                        "icon-color-government-id-type":null,
                        "icon-style":null,
                        "input-style":null,
                        "page-transition":null,
                        "text-align":null,
                        "text-decoration-line-link":null,
                        "us-state-input-method":null,
                        "vertical-options-style":null,
                        "government-id-pictograph-position":null,
                        "id-back-pictograph-height":null,
                        "id-back-pictograph-url":null,
                        "id-front-pictograph-height":null,
                        "id-front-pictograph-url":null,
                        "passport-front-pictograph-height":null,
                        "passport-front-pictograph-url":null,
                        "passport-signature-pictograph-height":null,
                        "passport-signature-pictograph-url":null,
                        "government-id-select-pictograph-height":null,
                        "government-id-select-pictograph-url":null,
                        "device-handoff-terms-text-position":null,
                        "selfie-pictograph-url":null,
                        "selfie-pictograph-height":null,
                        "selfie-terms-text-position":null,
                        "selfie-center-pictograph-url":null,
                        "selfie-center-pictograph-height":null,
                        "selfie-left-pictograph-url":null,
                        "selfie-left-pictograph-height":null,
                        "selfie-right-pictograph-url":null,
                        "selfie-right-pictograph-height":null,
                        "document-pictograph-position":null,
                        "document-pictograph-height":null,
                        "document-pictograph-url":null,
                        "camera-support-pictograph-height":null,
                        "camera-support-pictograph-url":null,
                        "loading-pictograph-height":null,
                        "loading-pictograph-url":null
                     }
                  }
               },
               {
                  "type":"inquiry-session",
                  "id":"iqse_WVV6Jyt3Xj8pfZ2dReAaPM7e",
                  "attributes":{
                     "status":"new",
                     "created-at":"2023-02-13T23:28:51.000Z",
                     "ip-address":null,
                     "user-agent":null,
                     "os-name":null,
                     "os-full-version":null,
                     "device-type":null,
                     "device-name":null,
                     "browser-name":null,
                     "browser-full-version":null,
                     "mobile-sdk-name":null,
                     "mobile-sdk-full-version":null,
                     "is-proxy":null,
                     "is-tor":null,
                     "is-datacenter":null,
                     "threat-level":null,
                     "country-code":null,
                     "country-name":null,
                     "region-code":null,
                     "region-name":null,
                     "latitude":null,
                     "longitude":null
                  },
                  "relationships":{
                     "inquiry":{
                        "data":{
                           "type":"inquiry",
                           "id":"inq_ZWRL8Dr2KjhHcewoj3meoDU3"
                        }
                     }
                  }
               },
               {
                  "type":"inquiry-session",
                  "id":"iqse_XJKSyAFd9ZuEzNXbPTSR5697",
                  "attributes":{
                     "status":"active",
                     "created-at":"2023-02-13T23:28:54.000Z",
                     "ip-address":"189.132.156.170",
                     "user-agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 14_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1 Mobile/15E148 Safari/604.1",
                     "os-name":"iOS",
                     "os-full-version":"14.5",
                     "device-type":"smartphone",
                     "device-name":"iPhone",
                     "browser-name":"Mobile Safari",
                     "browser-full-version":"14.1",
                     "mobile-sdk-name":null,
                     "mobile-sdk-full-version":null,
                     "is-proxy":false,
                     "is-tor":false,
                     "is-datacenter":false,
                     "threat-level":"low",
                     "country-code":"US",
                     "country-name":"United States",
                     "region-code":"CA",
                     "region-name":"California",
                     "latitude":37.751,
                     "longitude":-97.822
                  },
                  "relationships":{
                     "inquiry":{
                        "data":{
                           "type":"inquiry",
                           "id":"inq_ZWRL8Dr2KjhHcewoj3meoDU3"
                        }
                     }
                  }
               }
            ]
         },
         "created-at":"2023-02-13T23:29:15.893Z"
      }
   }
}

Event - root JSON API object
Each event has payload - nested JSON API object

Generic support

Hi. Thanks for the nice package.
I want to use it, but I think it would be good to add supporting of generic.
What do you think?

For Example:

const jsona = new Jsona();
jsona.deserialize<MyModel[]>(apiResponse);

Get meta from the API

Hi, everyone! I have the following JSON:

{
  "data": [
    {
      "id": "1",
      "type": "formations",
      "links": {
        "self": "https://localhost.com/api/v1/formations/1"
      },
      "attributes": {
        "title": "Voluptatibus magni maiores et qui aut commodi tempore.",
        "text_content": "Debitis ipsa odio iusto ut ea consectetur perspiciatis. Quae ea voluptatem ut atque velit. Nobis quia et quod corrupti.",
        "audio_url": null,
        "favorited": false,
        "cover_url": null,
        "cover_attachment_id": null,
        "audio_attachment_id": null
      }
    },
    {
      "id": "2",
      "type": "formations",
      "links": {
        "self": "https://localhost.com/api/v1/formations/2"
      },
      "attributes": {
        "title": "Nihil pariatur deserunt non optio temporibus.",
        "text_content": "Qui architecto enim. Beatae sed molestias facilis voluptates dolorem. Repellat dolor voluptatum. Commodi dolorem enim.",
        "audio_url": null,
        "favorited": false,
        "cover_url": null,
        "cover_attachment_id": null,
        "audio_attachment_id": null
      }
    }
  ],
  "meta": {
    "total_count": 2
  },
  "links": {
    "first": "https://localhost.com/api/v1/formations?page%5Bnumber%5D=1&page%5Bsize%5D=10",
    "last": "https://localhost.com/api/v1/formations?page%5Bnumber%5D=1&page%5Bsize%5D=10"
  }
}

I'm using the deserialize function to normalize the attributes but I need to have this meta: {total_count: 2} normalized as well.

Is this possible? I searched in the "Customize" section and tried to implement this for an hour but I didn't succeed.

Thanks for your help!

Deserializer uses wrong cache if array of entities does not have id

In https://github.com/olosegres/jsona/blob/master/src/builders/JsonDeserializer.ts#L56 type and id are used as key in a local to avoid dezeriazling the same object more than once.

I a scenario where the entities does not have an id yet, which is allowed upon creation of resources, this leads to a fake cache hit and thus an array of the same objects.

Example:

{
  "data": [
    {
      "type": "language-knowledges",
      "relationships": {
        "sourceLanguage": {
          "data": {
            "type": "source-languages",
            "id": "2b86b376-07aa-45ab-9287-5e3fcdfb34df"
          }
        },
        "workArea": {
          "data": {
            "type": "work-areas",
            "id": "843b6b88-0c4e-4078-bedb-72ec4d4a19f5"
          }
        }
      }
    },
    {
      "type": "language-knowledges",
      "relationships": {
        "sourceLanguage": {
          "data": {
            "type": "source-languages",
            "id": "2caa0dc2-877c-4667-80e0-869707ad3ad1"
          }
        },
        "workArea": {
          "data": {
            "type": "work-areas",
            "id": "843b6b88-0c4e-4078-bedb-72ec4d4a19f5"
          }
        }
      }
    }
  ]
}

deserializes to

[ { type: 'language-knowledges',
    id: undefined,
    sourceLanguage:
     { type: 'source-languages',
       id: '2b86b376-07aa-45ab-9287-5e3fcdfb34df' },
    workArea:
     { type: 'work-areas',
       id: '843b6b88-0c4e-4078-bedb-72ec4d4a19f5' },
    relationshipNames: [ 'sourceLanguage', 'workArea' ] },
  { type: 'language-knowledges',
    id: undefined,
    sourceLanguage:
     { type: 'source-languages',
       id: '2b86b376-07aa-45ab-9287-5e3fcdfb34df' },
    workArea:
     { type: 'work-areas',
       id: '843b6b88-0c4e-4078-bedb-72ec4d4a19f5' },
    relationshipNames: [ 'sourceLanguage', 'workArea' ] } ]

Note the id's of the source-languages.

It can be worked around by assigning and id before deserializing.

It would be good if Jsona either used a more robust key for the cache, or threw an error on a missing id.

Thanks for supplying this great library!

How to deserialize duplicate id without having duplicate result?

I have this JSON API data format:

{
  "meta": {
    "page": {
      "current-page": 1,
      "per-page": 25,
      "from": 1,
      "to": 2,
      "total": 2,
      "last-page": 1
    }
  },
  "data": [{
      "type": "brands",
      "id": "105",
      "attributes": {
        "name": "Brand A",
        "status": "pending",
        "company_id": 1
      },
    },
    {
      "type": "brands",
      "id": "105",
      "attributes": {
        "name": "Brand A",
        "status": "approved",
        "company_id": 2
      },
    }
  ]
}

But when I perform jsona.deserialize(data), I get this as result:

[{
  type: "brands"
  id: "105"
  name: "Brand A"
  status: "pending"
  company_id: 1
},
{
  type: "brands"
  id: "105"
  name: "Brand A"
  status: "pending"
  company_id: 1
}]

RangeError: Maximum call stack size exceeded with jsona 1.10.1 not on older releases

For a decoupled Drupal project we use next-drupal node.js module in combination with next Drupal module. Next-drupal uses jsona for deserialazation. Next-drupal 1.5 (latest release) bundles jsona 1.10.1.

With this version some of our pages give errors like these:

error RangeError: Maximum call stack size exceeded at JsonPropertiesMapper.setAttributes (/node_modules/next-drupal/node_modules/jsona/lib/simplePropertyMappers.js:79:61) at JsonDeserializer.buildModelByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:56:25) at JsonDeserializer.buildRelationsByData (//node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:94:46) at JsonDeserializer.buildModelByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:67:38) at JsonDeserializer.buildRelationsByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:94:46) at JsonDeserializer.buildModelByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:67:38) at JsonDeserializer.buildRelationsByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:89:53) at JsonDeserializer.buildModelByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:67:38) at JsonDeserializer.buildRelationsByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:94:46) at JsonDeserializer.buildModelByData (/node_modules/next-drupal/node_modules/jsona/lib/builders/JsonDeserializer.js:67:38)

Before next-drupal did not bundle jsona with its node.js module and we used jsona 1.9.7. If I simply downgrade jsona to 1.9.7 within the next-drupal folder the error is gone and the page in question loads just fine.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.