Serde icon indicating copy to clipboard operation
Serde copied to clipboard

2-level object arrays

Open geek-merlin opened this issue 2 months ago • 5 comments

Problem: Multi-level object arrays

I'm running into another limit with a 2-level array like this:

class Ledger {
  /**
   *  @param InfoObject[][] $info,
   */
  public function __contruct(
    private readonly array $info,
  ) {}
  public function getInfo(string $account, string $currencyCode) {
    return $this->info[$account][$currencyCode];
  }
}

I don't see how to describe this to serde, and am going to work around it w/ some boilerplate for an internal intermediate object that limits the arrays to 1 level.

geek-merlin avatar Dec 18 '25 12:12 geek-merlin

Thoughts on how to approach

Related to #41 ArrayShapes. I think looking at the most general case is not a "YAGNI" here.

What about sth like this:

class MyArrayShape implements ArrayShapeInterface {
  public function getKeyType(int|string ...$keys): KeyType {
    return match (count($keys)) {
      default => Type::String,
    };
  }
  public function getValueType(int|string ...$keys): Type|string {
    return match (count($keys)) {
      0 => Type::Array,
      1 => Type::Array,
      2 => Info::class,
    };
  }
}

The above is the imagined answer to my use case above. How "ArrayShapes" are modeled should be obvious.

PropertyPath instead of scope

Why i am spelling this out is, that is see two directions to approach the problem of "hey, on unserialization, what type should i map to":

  • the one i see currently in serde: Scope-based (as a kind of state machine approach)
  • a propertyPath based one

And i think the latter is much easier to grok and audit. I'd really like to see sth like this in array shapes. And maybe in the serde API for object properties too.

EDIT: The above as scope-based

class MyArrayShape implements ScopedArrayShapeInterface {
  public function getKeyType(int|string $key, string $scope): KeyType {
    return Type::String;
  }
  public function getValueType(int|string $key, string $scope): Type|string {
    return match ($scope) {
      'level2' => Info::class,
      default => Type::Array,
    };
  }
  public function getChildScope(int|string $key, string $scope): Type|string {
    return match ($scope) {
      '' => 'level1',
      'level1' => 'level2',
      default => '',
    };
  }
}

geek-merlin avatar Dec 18 '25 13:12 geek-merlin

Currently this is explicitly not supported. I never did figure out a way to describe deep arrays that include objects, and IMO that's a sign that your data model is bad to begin with. (Or the data model of the 3rd party you're ingesting is bad. Which may be more common.) Looking at ArrayShape was mostly a passing thought; I've not actually looked into it.

A possible workaround would be to let it deserialize into a deeply nested array, then use a PostLoad callback to finish the job and upcast parts of the array in place.

I'm not sure what you mean by property path, though. Can you clarify?

Crell avatar Dec 18 '25 15:12 Crell

a sign that your data model is bad to begin with. I am the first who dislikes bad typing, and my thoughts on the array-shape issue was, this deserves to be un-supported. I disagree though with 2-level class-internal typed array. Yes it is not clean, and in the absence of php typed collections and generics, there's many use cases where this is the least paid.

That said, i can easily replace this with more boilerplate, or replace [$foo][$bar] w/ ["$foo:$bar"] and be done, because i control the code. So not much pain about this on my side.

(PS: I did read your generics artivle. Rock on! ❤️ )

geek-merlin avatar Dec 18 '25 22:12 geek-merlin

I'm not sure what you mean by property path, though. Can you clarify?

I have edited the above comment and added "EDIT: The above as scope-based". This should clarify the difference between a scope-based and property-path-based tree access API architecture. Just as an inspiration for further evolution.

geek-merlin avatar Dec 18 '25 22:12 geek-merlin

I'm still not following... An array shape attribute would be more akin to what PHPStan does with shapes in docblocks. You would never write your own, except maybe as a way to reuse one?

Crell avatar Dec 19 '25 20:12 Crell