Offroaders123 / NBTify

A library to read and write NBT files on the web!
http://npm.im/nbtify
MIT License
42 stars 5 forks source link

Less-Strict Write Handling #20

Closed Offroaders123 closed 1 year ago

Offroaders123 commented 1 year ago

Inspired, related to, and continued from the later paragraphs of #19, I'm considering making the writing process less strict. This seems like a backwards step, but I think it could make it a bit more dynamic, and a little more flexible. It also make things more consistent from the JavaScript side of things.

One of my struggles with pushing x JavaScript data structures to be NBT serializable is that not all JavaScript features go hand in hand with the NBT format spec. Namely, how JavaScript allows multiple-type items in arrays, compared to allowing only a single type for all items in NBT List tags.

My thoughts on making things more dynamic, and less strict, are because I'm not sure if I can completely, and securely validate all of the data going into the library, and I'm not sure if I want to force what kinds of data structures people may be trying to write with the library. I can add checks to prevent things from going in, but that may cause unexpected things to happen, which I also don't want. Either way, something unexpected is probably going to happen, so I think I'd rather it work correctly if possible.

I'm not sure if I like that last paragraph exactly, as it's not quite in the same words to what I'm thinking. Essentially, I want NBTify to just handle any data you throw at it, without having to do anything custom just to make it not error out (That's a better mindset explanation :) ). If the data you try to put in to the library isn't correct, then you have to handle how the data should be serialized. NBTify will try it's best to put the data into the NBT format spec the best as it can.

I think the JSON module is really cool in that you can pretty much throw anything at it, and it will serialize everything that it can, and coalesce the other things it finds to what primitives it does support (JSON.stringify() documentation). This essentially happens to objects only, with it's accessible properties. If there aren't any properties to serialize, then I think that's where the empty { } value comes from in the stringified JSON output. This seems fairly extensible and easy to understand/work with. The only non-JSON serializable primitive is BigInt, and that one will throw an error if it is encountered as a property value with JSON.stringify().

Kind of wrote a lot there, and I'm not sure if I summed everything up completely, so I think I'll leave it at that :)

Offroaders123 commented 1 year ago

Writing existing objects to NBT using NBTify is now much more open to what it allows in. I think I have settled on a middle ground that falls somewhere into how the JSON module handles incoming data.

NBTify's NBT data structure handling is now fully based of of vanilla JavaScript primitives, so I don't have to worry about specific object types coming into the Write module, since it will accept anything you throw at it (#19). If you throw something broken at it, and it's not something nice and neat like a JSON-ready object, then it will do it's best at doing something with it. Say if you pass in a Number object, rather than a number primitive, NBTify will simply parse that as an object, or Compound tag. It's not going to fix unexpected values to make them more streamlined (turning that Number into a number). I want it to be up to the user to know what the data they're working with is ahead of time, then make the necessary changes to it so they know exactly what comes out of it.

So for things like passing in List tags with inconsistently typed items, I'm going to throw errors for that, since that's not something that I want NBTify to handle for you (#21). There's a TON of broken behavior that will come out of relying on that, and it's definitely not something to rely upon if you know that it works that way.

Same with circular data structures (#22). NBTify isn't going to keep references to circular object references for you, nor is it going to remove those entries from the parent object with that reference. That can also lead to silly issues that don't need to be something that falls by the wayside.

I guess I'm forcing a strong take on this since I want to ensure that I follow this wherever possible. There's likely to be more scenarios that I find where it falls into gray areas like this, and if you start to allow inconsistencies into the mindset that you're trying to follow initially, then it starts to cause other issues too. I wanted to write all of these guidelines out so I can reference them again later if needed if I'm not sure why I decided what I did. It's another case where I chose this for a reason, not just for choosing one way for the sake of it.

As an end summary (from the library's point of view): When in doubt, don't coalesce things you don't expect. If something isn't structured the way it's supposed to, don't fix it. That's up to the user to do, as it's their data that you are working with! It's not up to you to tell if something is intentional or not. Only the user does/doesn't know what is. It's up to you to provide a consistent 1:1 input-output experience for whatever given data is being provided by the user. This allows them to depend on you for your consistent behavior, which is what makes you a strong building block in the user's toolbox to create awesome things!