import * as GeodeData from "../../geodes.json" assert { type: "json" }
export const GeodeList = GeodeData as Array<Geode>
export interface Geode {
name: string
location: string
price: GeodePrice
drops: Array<GeodeDrop>
}
export interface GeodePrice {
stat: string
amount: number
}
export interface GeodeDrop {
stat: string
odds: number
}
export type GeodeName = typeof GeodeList[number]["name"]
// TypeError: GeodeList.map is not a function. (In 'GeodeList.map((geode) => geode.name)', 'GeodeList.map' is undefined)
export const getGeodeNames = (): Array<GeodeName> => GeodeList.map((geode) => geode.name)
export const getGeodeByName = (name: GeodeName): Geode | undefined => GeodeList.find((geode) => geode.name === name)
#thje GeodeList.map is not a function
209 messages ยท Page 1 of 1 (latest)
๐ GeodeList.map is not a function
because GeodeList is undefined
because the import is not right
๐ json imports require a named import
because you are only importing one object
it's not some kind of namespace
so import GeodeData from "../../geodes.json" assert { type: "json" };
๐ also export type GeodeName = typeof GeodeList[number]["name"] won't give you the proper type you expect
since for GeodeList you used a cast
so GeodeList: Array<Geode>
so GeodeName: Array<Geode>[number]["name"]
so GeodeName: Geode["name"]
so GeodeName: string
and not literals
๐ a few other things:
- use camelCase for variables names (
geodeList) - you don't need to repeat the type when it can be inferred
- you can use
[]instead ofArray<>(const geodeList: Geode[])
Preview:```ts
import geodeData from "../../geodes.json" assert {type: "json"}
export interface Geode {
name: string
location: string
price: GeodePrice
drops: GeodeDrop[]
}
export interface GeodePrice {
stat: string
amount: number
}
export interface Geode
...```
You can choose specific lines to embed by selecting them before copying the link.
@torn shore
- i use what i want to smh :c
- right
- yeah ik I like
Array<T>more
Okay thank you, honestly that makes sense but the error didn't say anything even remotely close to the import being wrong
Hm
Then what to do if I want a union type of the names of geodes
because the import isn't "wrong"
it's only wrong in your case in the way you use it
but it leads some elements being undefined
which causes the issue somewhere else in the file
have a look at the playground link I sent
Wrong in the way I use it yes that's what I meant by wrong
I did
I don't see any other big changes than the import
Ohhhh
I see now
I mean, that import is valid at compile time
but not at runtime
that's why the error seems unrelated
The variable is gone
well not gone but it's not where it was
and it's not casted anymore
you can directly tap into the import
no need to copy it to some other variable tbh
no but you still lose type info
no cast, but you still put the type on the variable
you could also have
export const geodeList = geodeData satisfies Geode[];
export type GeodeName = typeof geodeList[number]["name"];
I honestly forgot satisfies was even a thing cause I never even knew where and how to use it
but tbh geodeList and geodeData are duplicate
Yeah but you can't export an import can you
you could name your import geodeList and use it to get the names directly
wdym?
import geodeList from "../../geodes.json" assert { type: "json" };
export type GeodeName = typeof geodeList[number]["name"];
but you don't really have a check using the satisifes operator
you changed the source of truth
from the satisfies Geode[] to the content of the JSON file
I don't know what satisfies is
Okay so
How do I make it so that getGeodeByName knows what names are valid
TypeScript 4.9 Release Notes
gave you 3 different solutions already 
import geodeData from "../../geodes.json" assert { type: "json" };
const geodeList: Geode[] = geodeData;
export type GeodeName = typeof geodeData[number]["name"];
```or
```ts
import geodeData from "../../geodes.json" assert { type: "json" };
export const geodeList = geodeData satisfies Geode[];
export type GeodeName = typeof geodeList[number]["name"];
```or
```ts
import geodeList from "../../geodes.json" assert { type: "json" };
export type GeodeName = typeof geodeList[number]["name"];
which are all pretty similar
I think the first one will erase information because it is typed as Geode[], which is a mutable array type
The other 2 options might preserve the information. Although I haven't used JSON imports.
first one uses geodeData as source
not geodeList
so no type is erased
1 and 2 provide some additional safety
checking what is imported before using it
while solution 3 doesn't and uses that as source of truth
2nd also doesn't work
So does TS type geodeData correctly based on the JSON file?
what type is the geodeData variable that you import?
wait, now you ask about it
not sure if strings are infered as literals or not 
It's JSON
might actually be the problem here
It's an array of geodes like these
That's annoying
It is
Is the json file an array, with geodes in it?
yeah you said it is
Does it need to be json?
Yeah it seems that name is string, and all array entries are optional too
It's just { name: string }[]
yes
Uh yeah
Will the JSON data change sometimes?
kinda big
Not while running no
JSON isn't smaller that JS, it's probably larger
Will it change while the program is not running, but without your updating the code?
No
OK
I think you just need to use a .js file instead
export const geodeList = [
{ name: 'foo },
...
] as const
the as const will give you more info
export default instead if you want to
JSON is valid JS, so you don't have to change anything with the data
Yeah I know it is, it just seems odd doing that
why?
Otherwise you need a way to get TS to infer the type of the JSON as const
npm
A (very) simple CLI tool that reads JSON files and creates .d.ts files with their keys/values explicitly defined. Latest version: 1.0.7, last published: 3 years ago. Start using ts-json-as-const in your project by running npm i ts-json-as-const. There are no other projects in the npm registry using ts-json-as-const.
You can use a tool to generate d.ts files to describe your json. Or just do it the easy way
Yeah
obviously it does need to be .ts with TypeScript
Gut
hol on how do I work this
you could accept string in getGeodeByName
then you have to search geodes to see if there is a match. I don't know how your lookup works
maybe you convert the array to an object somewhere
nah, it's just Array.find
so if you use (name: string) does that work?
It must just return undefined or something if it doesn't find it
export const getGeodeByName = (name: string): Geode | undefined => GeodeList.find((geode) => geode.name === name)
Type '{ readonly name: "Mint"; readonly location: "Minty Grooves"; readonly price: { readonly stat: "Mint"; readonly amount: 2000; }; readonly drops: readonly [{ readonly stat: "Mint"; readonly odds: 2; }, { readonly stat: "Alpha Point"; readonly odds: 2; }, ... 5 more ..., { ...; }]; } | ... 33 more ... | undefined' is not assignable to type 'Geode | undefined'.
Type '{ readonly name: "Mint"; readonly location: "Minty Grooves"; readonly price: { readonly stat: "Mint"; readonly amount: 2000; }; readonly drops: readonly [{ readonly stat: "Mint"; readonly odds: 2; }, { readonly stat: "Alpha Point"; readonly odds: 2; }, ... 5 more ..., { ...; }]; }' is not assignable to type 'Geode'.
Types of property 'drops' are incompatible.
The type 'readonly [{ readonly stat: "Mint"; readonly odds: 2; }, { readonly stat: "Alpha Point"; readonly odds: 2; }, { readonly stat: "Chestnut"; readonly odds: 5; }, { readonly stat: "Bat"; readonly odds: 8; }, { ...; }, { ...; }, { ...; }, { ...; }]' is 'readonly' and cannot be assigned to the mutable type 'GeodeDrop[]'
seems to be a problem with your return type annotation
doesn't sem to be
I mean what else can ```ts
GeodeList.find((geode) => geode.name === name)
Maybe GeodeDrop[] should be readonly GeodeDrop[]
I don't know what the Geode type is
But, you can just remove the return type annotation, right?
import GeodeList from "../GeodeList"
export interface Geode {
name: string
location: string
price: GeodePrice
drops: Array<GeodeDrop>
}
export interface GeodePrice {
stat: string
amount: number
}
export interface GeodeDrop {
stat: string
odds: number
}
export type GeodeName = typeof GeodeList[number]["name"]
export const getGeodeNames = () => GeodeList.map((geode) => geode.name)
export const getGeodeByName = (name: string): Geode | undefined => GeodeList.find((geode) => geode.name === name)
and TS will infer the return type
Then everything else breaks
really?
const geodeName = commandMessage.args[0]
const longNumbers = commandMessage.hasFlag("long-numbers")
const targetGeode = Geodes.getGeodeByName(geodeName)
if (!targetGeode) {
await commandMessage.reply(BotMessage.create(Prefix.ERROR, INVALID_GEODE_NAME))
return
}
await commandMessage.replyWithEmbed(this.getGeodeEmbed(targetGeode, longNumbers))
see this is one of the uses
export interface Geode {
name: string
location: string
price: GeodePrice
drops: Array<GeodeDrop>
}
to
export interface Geode {
name: string
location: string
price: GeodePrice
drops: readonly Array<GeodeDrop>
}
Well you're gonna have to go through and fix errors until it works ๐
'readonly' type modifier is only permitted on array and tuple literal types.ts(1354)
Oh
Yeah
ReadonlyArray<GeodeDrop> ?
Ooooh right
Niceee that works
Makes sense
Thank you autocomplete, that's exactly what I wanted here
(name: string & GeodeName)
I just kinda casted it but I don't think that's the way to go...?
what happens now?
Hmm
Oh I tried a union but not an intersection interesting
or (name: string & GeodeName & {}
I'm sure there is a trick to making it autocomplete
right
GeodeName | (string & {})
this should be it
yeah & would require matching both, so it would erase string
Oh, that works!
Yeah thanks to Mr Pocock
Oooooohh!!
any whoever discovered the trick
Another option is to check whether the input in a correct name before finding. This lets you make a find function that always returns a match, never undefined, which is quite nice
You would use a type guard function there probably
anyhow, I'm off
I mean I kinda tried but it always turned out horrible, so not today I guess
Bye, thanks for helping
Bye
function IsGeodeName(s: string): s is GeodeName {
return (geodeNames.includes(s))
}
oh.
But you need to generate an array of geodeNames first
I don't think you wannt to manually make a list here, too easy to make a mistake
but you can generate a list from your data. You can also generate an object from your data.
Then you can have
const geodeObject = {
"diamond": { ... }
}
then you don't need your findGeodeByName thing, because you can just write geodeObject.diamond
You just loop through your geodesList and make an object
But you probably need a mapped type to avoid losing info, which is a bit complicated to write at your level
If you write your data (that was JSON) as an object to begin with, that's easier...
not sure if it needs to be an array
Why not
I mean the way I use it, apart from this, it would be better off as an array I think
Okay thanks you both, I'm good with what I have rn
that's a default import, btw
Is there another way to import json?