I would use built-in lists for any lists (uniform variable-length sequences of objects).
I would use namedtuple-derived classes to store data with known fixed sets of named fields. It's like objects, but immutable.
So you'd have your text.tokens[3].part_of_speech == NOUN
, but won't have text.tokens[3].part_of_speech = NOUN
. Immutability is often useful at preventing a whole class of errors, though.
E.g.
from collection import namedtuple
Token = namedtuple('Token', ['part_of_speech', 'whatnot'])
Text = namedtuple('Text', ['tokens', 'metainfo', 'stats'])
# ...
json_doc = json.load(...)
tokens = []
for json_token in json_doc['tokens']:
t = Token(json_token['part_of_speech'], json_token['whatnot'])
# or even t = Token(**json_token)
tokens.append(t)
...
return Text(tokens, ...)