haxetink / tink_json

Macro powered JSON.
The Unlicense
34 stars 9 forks source link

Integer constants as enums #42

Open rhacking opened 6 years ago

rhacking commented 6 years ago

As far as I can see, there's no way of parsing target as an enum in the following example:

    "bufferViews": [
        {
            "buffer": 0,
            "byteOffset": 576,
            "byteLength": 72,
            "target": 34963
        },
        {
            "buffer": 0,
            "byteOffset": 0,
            "byteLength": 576,
            "byteStride": 12,
            "target": 34962
        }
    ],

The following results in [haxe] invalid use of @:json

enum BufferTarget {
    @:json(34963) ElementArrayBuffer;
    @:json(34962) ArrayBuffer;
}

Right now I'm using an enum abstract, but I think it would be more elegant if something like the above were possible.

back2dos commented 6 years ago

Well, the way it's "supposed to work" is more like:

enum Buffer {
    @:json({ target: 34963 }) ElementArrayBuffer(elementArrayBuffer:{ buffer:Int, byteOffset:Int, byteLength:Int });
    @:json({ target: 34962 }) ArrayBuffer(arrayBuffer:{ buffer:Int, byteOffset:Int, byteLength:Int, byteStride:Int });
}

The point being that the byteStride is actually only there if needed. But I'll look into making it possible this way ;)

rhacking commented 6 years ago

Hmm, I can see why it would be better to do it like this (as indeed byteStride is only there for ArrayBuffer), but then you can't access, say, the buffer property of an arbitrary buffer without first using a switch to extract it, even though any buffer will have this property.

back2dos commented 6 years ago

Well, could solve that with abstracts or static extensions, but without really knowing the use case I can hardly give qualified advise ^^

That said, I will look into making this possible per @:json. Right now you can do something like this:

//BufferTarget.hx
package whatever;

class BufferTargetParser {
  public function new(_) {}
  public function parse(value:Int):BufferTarget
    return switch value {
      case 34963: ElementArrayBuffer;
      case 34962: ArrayBuffer;
      default: throw 'Invalid buffer target $value';
    }
}
@:jsonParse(whatever.BufferTarget.BufferTargetParser)//must be fully qualified name
enum BufferTarget {
    @:json(34963) ElementArrayBuffer;
    @:json(34962) ArrayBuffer;
}

Here are the tests for custom parsing and stringifying

rhacking commented 6 years ago

Ah, I didn't know you could that. I solved it like this now:

typedef BufferViewRaw = {
    var buffer : Int;
    var byteLength : Int;
    var byteOffset : Int;
    var target : Int;
    @:optional var byteStride : Int;
}

@:jsonParse(gltf.GLTFLoader.BufferViewParser)
typedef BufferView = {
    var buffer : Int;
    var byteLength : Int;
    var byteOffset : Int;
    var target : BufferTarget;
}

class BufferViewParser {
    public function new(_) {}

    public function parse(v:BufferViewRaw):BufferView {
        return {
            buffer: v.buffer, 
            byteLength: v.byteLength, 
            byteOffset: v.byteOffset, 
            target: switch v.target {
                case 34963: ElementArrayBuffer;
                case 34962: ArrayBuffer(v.byteStride == null ? Tight : Fixed(v.byteStride));
                default: throw 'Invalid buffer target ${v.target}';
            }
        };
    }
}

enum BufferTarget {
    ElementArrayBuffer;
    ArrayBuffer(stride : Stride);
}

It's a bit bulky this way, but I think the final type it gets makes more sense. Though the :json modification would still be helpful for standalone enums that don't influence which other fields there are.