tomland icon indicating copy to clipboard operation
tomland copied to clipboard

Top-level Tables

Open pnotequalnp opened this issue 3 years ago • 3 comments

This TOML

[foo]
field = "bar"

[baz]
field = "quux"

is equivalent to this JSON

{
  "foo": {
    "field": "bar"
  },
  "baz": {
    "field": "quux"
  }
}

This data maps cleanly to this Haskell representation

type Data = Map String Entry

data Entry = Entry { field :: String }

However, I cannot figure out how to parse this shape of data with tomland. I threw together a quick repo to demonstrate my attempts and problems. The exact errors are in the CI run. I'm not sure if I'm misunderstanding how the library is supposed to be used, or if this sort of data is simply not able to be parsed by it currently.

Additionally, Map1 fails to decode the data that it just encoded. I'm not sure why that is, or if it's something I'm missing or a bug in the library. Map3, which should work from my understanding, also has curious behavior in regard to parsing equivalent TOML files differently (in particular, one being an error with the -Exact decoding variants).

pnotequalnp avatar Apr 10 '22 00:04 pnotequalnp

If I understand it correctly, you need to use the BiMap related functionality rather than the preexisting TomlCodec related functionality here, because a TomlCodec requires looking at a specific key (or multiple specific keys). c.f. https://hackage.haskell.org/package/tomland-1.3.3.1/docs/Toml-Codec-Combinator-Common.html#v:match. You can build your own alternative to match that does not look at a key however. I think that is the way to go here.

In the TOML datastructure itself, the root datastructures are introduced by the tree fields 'tomlPairs', 'tomlTables' and 'tomlTableArrays'. You are able to read/write these in a custom function that takes a bimap as input an creates a TomlCodec as output.

Qqwy avatar Oct 01 '22 14:10 Qqwy

I'm having trouble coming up with the solution described here, could anyone provide an example?

I've only been able to parse a file like this by calling parse, then manually iterating over the tomlTables and calling runTomlCodec on each individual value. Which leaves something to be desired in terms of validation etc.

thomasjm avatar Apr 06 '23 19:04 thomasjm

I had trouble with this, too. I followed @thomasjm's advice, and ended up with these helpers to get me unblocked. Pasting here as a starting point in case anyone else runs into a similar situation.

keyText :: Toml.Key -> T.Text
keyText = F.fold . cleanup . map Toml.unPiece . F.toList . Toml.unKey
  where
    -- Top-level table names are parsed as @"name" :|
    -- ["name"]@. Remove that duplication here.
    cleanup [x, y] | x == y = [x]
    cleanup x = x

-- | Parse a TOML file that is a top-level table whose values are all
-- the same type. The @tomland@ codec API is centered around starting
-- with a key, but a top-level table does not have a key, so we must
-- use the lower level 'Toml.parse' and 'Toml.tomlTables' before
-- repeatedly applying the provided 'Toml.TomlCodec'.
parseFileOf :: forall a. Toml.TomlCodec a -> T.Text -> Either [T.Text] [(T.Text, a)]
parseFileOf codec =
    first (map Toml.prettyTomlDecodeError)
        . validationToEither
        . traverse (uncurry go)
        . Toml.toList
        . Toml.tomlTables
        . either (error . show) id
        . Toml.parse
  where
    go :: Toml.Key -> Toml.TOML -> Validation [Toml.TomlDecodeError] (T.Text, a)
    go k v = (keyText k,) <$> Toml.runTomlCodec codec v

acowley avatar Apr 30 '23 19:04 acowley