#Reading Struct from JSON

35 messages Β· Page 1 of 1 (latest)

merry vigil
#

When I persist an instance of my Struct in a JSON, and then I load it, it seems like the types of the Struct properties are lost, even though I'm indicating the type and my code editor (VS Code) shows the properties properly typed, but when I access a method on runtime, I get the following error:

                     ^

TypeError: post.allPostsCounter.assertEquals is not a function
    at file:///home/chrlyz/proof_of_events/build/src/read.js:3:22

What would be the proper way to do something like this?

#

Here is an example using my Struct:

import { Struct, PublicKey, CircuitString, Field, Poseidon, PrivateKey } from "o1js";
import fs from 'fs/promises';

export class PostState extends Struct({
  posterAddress: PublicKey,
  postContentID: CircuitString,
  allPostsCounter: Field,
  userPostsCounter: Field,
  postBlockHeight: Field,
  deletionBlockHeight: Field,
}) {
  hash(): Field {
    return Poseidon.hash(
      this.posterAddress
        .toFields()
        .concat([
          this.postContentID.hash(),
          this.allPostsCounter,
          this.userPostsCounter,
          this.postBlockHeight,
          this.deletionBlockHeight,
        ])
    );
  }
}

const key = PrivateKey.random();
const address = key.toPublicKey();

const post = new PostState({
    posterAddress: address,
    postContentID: CircuitString.fromString(
        'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi'
      ),
      allPostsCounter: Field(1),
      userPostsCounter: Field(1),
      postBlockHeight: Field(777),
      deletionBlockHeight: Field(0)
});

const postJSON = JSON.stringify(post);
await fs.writeFile('./build/src/post.json', postJSON, 'utf-8');
import fs from 'fs/promises';
import { PostState } from './write';
import { Field } from 'o1js';

const post: PostState = JSON.parse(
    await fs.readFile('./build/src/post.json', 'utf8')
);

// post.allPostsCounter.assertEquals(1); // Throws error

Field(post.allPostsCounter).assertEquals(1); // This works
shadow oxide
#

This way it works:

import {
  Struct,
  PublicKey,
  CircuitString,
  Field,
  Poseidon,
  PrivateKey,
} from "o1js";
import fs from "fs/promises";

export class PostState extends Struct({
  posterAddress: PublicKey,
  postContentID: CircuitString,
  allPostsCounter: Field,
  userPostsCounter: Field,
  postBlockHeight: Field,
  deletionBlockHeight: Field,
}) {
  hash(): Field {
    return Poseidon.hash(
      this.posterAddress
        .toFields()
        .concat([
          this.postContentID.hash(),
          this.allPostsCounter,
          this.userPostsCounter,
          this.postBlockHeight,
          this.deletionBlockHeight,
        ])
    );
  }
  toJSON(): any {
    return {
      posterAddress: this.posterAddress.toBase58(),
      postContentID: this.postContentID.toString(),
      allPostsCounter: this.allPostsCounter.toJSON(),
      userPostsCounter: this.userPostsCounter.toJSON(),
      postBlockHeight: this.postBlockHeight.toJSON(),
      deletionBlockHeight: this.deletionBlockHeight.toJSON(),
    };
  }
  static fromJSON(data: any): PostState {
    return new PostState({
      posterAddress: PublicKey.fromBase58(data.posterAddress),
      postContentID: CircuitString.fromString(data.postContentID),
      allPostsCounter: Field.fromJSON(data.allPostsCounter),
      userPostsCounter: Field.fromJSON(data.userPostsCounter),
      postBlockHeight: Field.fromJSON(data.postBlockHeight),
      deletionBlockHeight: Field.fromJSON(data.deletionBlockHeight),
    });
  }
}

async function main() {
  const key = PrivateKey.random();
  const address = key.toPublicKey();

  const post = new PostState({
    posterAddress: address,
    postContentID: CircuitString.fromString(
      "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
    ),
    allPostsCounter: Field(1),
    userPostsCounter: Field(1),
    postBlockHeight: Field(777),
    deletionBlockHeight: Field(0),
  });

  const writeData = JSON.stringify(post.toJSON(), (_, v) =>
    typeof v === "bigint" ? v.toString() : v
  )
    .replaceAll("},", "},\n")
    .replaceAll("[", "[\n")
    .replaceAll("]", "\n]");
  await fs.writeFile("post.json", writeData);
}

main();
import { PostState } from "./struct";
import { Field } from "o1js";
import json from "../post.json";

const post: PostState = PostState.fromJSON(json);

console.log(post.allPostsCounter.toJSON());
post.allPostsCounter.assertEquals(1); // This works

Field(post.allPostsCounter).assertEquals(1); // This works

However, if you use fs.readFile instead of import, it throws an error.

merry vigil
#

Hey, @shadow oxide. Thanks for your response!

Did you add resolveJsonModule": true in your tsconfig.json? I needed to add the option to be able to import the json, but now I'm getting the following error:

node:internal/errors:496
    ErrorCaptureStackTrace(err);
    ^

TypeError [ERR_IMPORT_ASSERTION_TYPE_MISSING]: Module "file:///home/chrlyz/proof_of_events/build/post.json" needs an import assertion of type "json"
    at new NodeError (node:internal/errors:405:5)
    at validateAssertions (node:internal/modules/esm/assert:95:15)
    at defaultLoad (node:internal/modules/esm/load:86:3)
    at nextLoad (node:internal/modules/esm/loader:163:28)
    at ESMLoader.load (node:internal/modules/esm/loader:603:26)
    at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
    at new ModuleJob (node:internal/modules/esm/module_job:64:26)
    at #createModuleJob (node:internal/modules/esm/loader:480:17)
    at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34)
    at async ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:79:21) {
  code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING'
}

Node.js v18.17.1
#

If I import it like:

import json from "../post.json" with { type: "json" };

Code editor complains:

'with' statements are not allowed in strict mode.

And if I import it like this:

Import assertions are only supported when the '--module' option is set to 'esnext' or 'nodenext'

Code editor complains:

Import assertions are only supported when the '--module' option is set to 'esnext' or 'nodenext'

#

Not sure how I should proceed

shadow oxide
merry vigil
# shadow oxide You are welcome! Yes, it's my tsconfig.json:

Thanks again, I'll look into the ts configuration. Just one more question. Why did you use this condition in the replacer function in stringify:

typeof v === "bigint" ? v.toString() : v

We don't have any bigints in this Struct, right? πŸ€”

shadow oxide
merry vigil
pseudo ravine
#

@merry vigil every Struct has a built-in fromJson method. With that it should work. You don't have to write your own as @shadow oxide suggested

#

PostState.fromJson(JSON.parse(...))

merry vigil
# pseudo ravine PostState.fromJson(JSON.parse(...))

Hey, @pseudo ravine, neat! Thanks for responding! When I do this:

const post: PostState = JSON.parse(
    await fs.readFile('./build/src/post.json', 'utf8')
);

The code editor shows:

Property 'hash' is missing in type '{ posterAddress: PublicKey; postContentID: any; allPostsCounter: Field; userPostsCounter: Field; postBlockHeight: Field; deletionBlockHeight: Field; }' but required in type 'PostState'.

Should I do the following instead to fully restore the type of my Struct (including its hash method)?

const post = JSON.parse(
    await fs.readFile('./build/src/post.json', 'utf8')
);

const fullyTypedPost = new PostState(post);

Or am I missing something?

pseudo ravine
merry vigil
pseudo ravine
#

No, you do need PostState.fromJSON

merry vigil
#

Oh, sorry, I copied the wrong snippet. I meant this:

  const post1 = PostState.fromJSON(
    JSON.parse(await fs.readFile('./build/src/post1.json', 'utf8'))
  );

  const p: PostState = new PostState(post1);
pseudo ravine
#

Isn't post1 already a PostState, so you don't need the constructor?

merry vigil
#

It's like if I'm only recovering the object with its values

pseudo ravine
#

Hm that's frustrating. If you ignore TS and override the type, does it work?

merry vigil
pseudo ravine
#

Is the hash() method present at runtime?

merry vigil
#

Oh, let me see

pseudo ravine
#

Yeah I remember this. There's no way for the Struct type to know the type of the class that extends it. That's why it can't give you the correct type

#

But I think at runtime it should be correct

merry vigil
#

It doesn't compile. I can't fool ts πŸ˜†

pseudo ravine
#

Lol come on πŸ˜„

merry vigil
#

I remember having this issue a long time ago, while reading actions and trying to access the method of the actionType, and just opted for instantiating a new object from the recovered action

pseudo ravine
#

So anyway I think passing it to the constructor is the way to go!

merry vigil
shadow oxide
# pseudo ravine PostState.fromJson(JSON.parse(...))

Agree. This is the amended code:

import {
  Struct,
  PublicKey,
  CircuitString,
  Field,
  Poseidon,
  PrivateKey,
} from "o1js";
import fs from "fs/promises";

export class PostState extends Struct({
  posterAddress: PublicKey,
  postContentID: CircuitString,
  allPostsCounter: Field,
  userPostsCounter: Field,
  postBlockHeight: Field,
  deletionBlockHeight: Field,
}) {
  hash(): Field {
    return Poseidon.hash(
      this.posterAddress
        .toFields()
        .concat([
          this.postContentID.hash(),
          this.allPostsCounter,
          this.userPostsCounter,
          this.postBlockHeight,
          this.deletionBlockHeight,
        ])
    );
  }
}

async function main() {
  const key = PrivateKey.random();
  const address = key.toPublicKey();

  const post = new PostState({
    posterAddress: address,
    postContentID: CircuitString.fromString(
      "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"
    ),
    allPostsCounter: Field(1),
    userPostsCounter: Field(1),
    postBlockHeight: Field(777),
    deletionBlockHeight: Field(0),
  });

  const writeData = JSON.stringify(post);
  await fs.writeFile("post.json", writeData);
}

main();
import { PostState } from "./struct";
import { Field } from "o1js";
import json from "../post.json";

const post: PostState = PostState.fromJSON(json) as PostState;

console.log(post.allPostsCounter.toJSON());
post.allPostsCounter.assertEquals(1); // This works

Field(post.allPostsCounter).assertEquals(1); // This works
#

Working variant with fs.readFile:

import { PostState } from "./struct";
import { Field } from "o1js";
import fs from "fs/promises";

async function main() {
  const post: PostState = PostState.fromJSON(
    JSON.parse(await fs.readFile("post.json", "utf-8"))
  ) as PostState;

  console.log(post.allPostsCounter.toJSON());
  post.allPostsCounter.assertEquals(1); // This works

  Field(post.allPostsCounter).assertEquals(1); // This works
}

main();
#

@merry vigil , does it solve your issue?

merry vigil