Hello style-dictionnary team,
We are having a small problem, not sure if it is a bug or if we are incorrectly using the project.
In our properties, we have colors defined something like:
{
"color": {
"space-gray": {
"value": "#ccc"
},
"space-gray-w50": {
"value": "#dadada"
}
}
}
As an output from the json-nested transform group, and more particularly from the name/cti/camel, I would expect to find as an output:
{
"color": {
"spaceGray": "#ccc",
"spaceGrayW50": "#dadada"
}
}
Unfortunately the keys of the values themselves aren't camelCased and I get:
{
"color": {
"space-gray": "#ccc",
"space-gray-w50": "#dadada"
}
}
Is that expected behaviour?
I see how we could try to nest things more so that the path is picked up better, but we would loose the ability to have the first one being only spaceGray and not spaceGrayBase or something similar.
Hmm that is expected behavior. Let me try to explain why.
Because design tokens are defined in a nested object structure in style dictionary, we need to be able to flatten that object for platforms and filetypes that expect a flat array of variables, like and an Android resource XML file, or a CSS variables file. Internally what happens is any node in the object structure that has a "value" attribute is added to an array of tokens. To be able to turn the object structure into variable names, Style Dictionary adds a path array on the token with the object path to get to that token. In your example the object path would be ['color', 'space-gray']. The name transforms that are included, such as name/cti/camel use this path to create a name attribute on the token, so after being transformed, the token would look like:
{
"name": "colorSpaceGray",
"value": "#ccc",
"path": ["color", "space-gray"]
}
Name transforms edit the "name" attribute on the token object itself, and not the overall object structure itself.
Nested formats will keep the object structure of your original JSON, but because the name transforms only affect the tokens themselves and not any other part of the object, that is why it is "space-gray" is not camel-cased. If you want to take a look at the raw, transformed style dictionary object, you can use the 'javascript/module' format, which just outputs the whole object.
Long story short, name transforms don't work like you expect in nested style formats, like json/nested (TBH I never realized that until you filed this issue) because it only modifying the token itself and not the key of the token in the overall object. You could achieve what you want by writing a custom format, or maybe the json/flat format might be good enough? In your case it would output:
{
"colorSpaceGray": "#ccc",
"colorSpaceGrayW50": "#dadada"
}
Let me know if that helps. Also open to suggestions on how to fix the issue of nested formats and name transforms.
Hey @dbanksdesign,
Thanks for the answer! Yeah I had a look at the internals and I guessed that the problem came from the fact that paths are different from names, and that difference comes from being able to resolve keys inside of values.
We actually like the nested part as we find it really practical to use in JavaScript, but the kebab-cased variable names are not super JavaScripty.
And yeah for now I solved it with an action, post-processing the result 馃檲
But I guess creating a format is more senseful.
Regarding fixing it, if it is considered to be a bug, I could have a look later on.
My first guess is a lot of it depends on where the key resolution in values happen?
The safest way could be to keep two paths: one original path which is used for resolving keys when resolving values, and one resolved path, where the name/cti transform had a chance to work its magic used to create the final data structure.
Otherwise there could also be a postprocessing of the keys after generating the structure?
But I guess creating a format is more senseful.
+1, probably a custom format would be the simplest solution in this case.
I'm going to break with the other comments here and claim that we should view this as a bug.
Why should the nested formats not transform the separate path sections each using the name transforms?
I understand from a technical / structure POV that it doesn't make sense; however it makes sense from a user POV - thus the reason the issue was raised to begin with. If the transformGroup is 'js' or another name transform is applied, why shouldn't it apply to the names being generated by the provided nested formats?
Looking for thoughts from @didoo and @dbanksdesign on my previous comment :)
I tend to agree with @chazzmoney here. Given the name of the transform, as a "consumer" probably I would expect what @jduthon was expecting too.
I had a quick look at the code, and I tried to modify the minifyDictionary function used in the 'json/nested' function. It's just a small change to do, one line of code, so I can pick it up easily.
Only thing, I want to get confirmation of the expected behaviour (paging also @dbanksdesign here). Since I'm using lodash camelCase function, as done in other parts of SD, this means that this object:
{
"test": {
"abc-def" : { "value": "1" },
"abc_def" : { "value": "2" },
"abc--def" : { "value": "3" },
"abc-def-w50" : { "value": "4" },
"abc-def-500" : { "value": "5" },
"ghi": {
"jkl-mno_pqr-789": { "value": "6" }
}
}
}
generates this _json/nested_ output:
{
"test": {
"abcDef": "3",
"abcDefW50": "4",
"abcDef500": "5",
"ghi": {
"jklMnoPqr789": "6"
}
}
}
In other words, are these the transformations we would expect?
test => test
abc-def => abcDef
abc_def => abcDef
abc--def => abcDef
abc-def-w50 => abcDefW50
abc-def-500 => abcDef500
ghi => ghi
jkl-mno_pqr-789 => jklMnoPqr789
Ah, I just noticed that in this process, I have demonstrated that is not so easy as expected! :)
Look at the keys abc-def, abc_def and abc--def, that in the src file are distinct entities, but once camelCased they have the same key, so we lose some key/values along the way.
@dbanksdesign @chazzmoney @jduthon what do we do? how do we solve this? do we have to write our own camelCase function that doesn't strip dashed, underscores, etc?
FYI, this is the same object, processed as _scss/variables_, and as you can see we have a similar problem:
$test-abc-def: 1;
$test-abc-def: 2;
$test-abc-def: 3;
$test-abc-def-w-50: 4;
$test-abc-def-500: 5;
$test-ghi-jkl-mno-pqr-789: 6;
so maybe we should consider it an "expected" behaviour, and proceed with the changes.
Let me know your thought, I can submit a PR with the changes I made for this test.
Great catch @didoo !
I鈥檒l pull together a PR on this bug. Here is what I have in mind:
It's tough, I can see both why we should and shouldn't do it. My hesitation is now transforms are not only being run on the tokens themselves but we are not putting them in the formats as well, which seems a bit messy. It kind of breaks what formats and transforms were intended to do, transforms modify a token, formats put the tokens together into a file.
Do we want all name transforms to take effect in all nested formats? If we do, is there a way to handle it automatically so we don't have to modify the formats and do some hardcoded checking of which name transforms to apply? Just thinking out loud..
And to @didoo's problem, yea I guess that has been an issue since the beginning of style dictionary. TBH, I don't think anyone would really run into that problem too much, but maybe it warrants a piece of documentation somewhere.
For this PR, it should definitely include tests for those outputs like @didoo did. Let's try to fix this, but my worry is it will be overly complex, hard to maintain, and might cause a lot of unforeseen issues. This also sounds like it probably should be in a major version release as this would break consumers of nested formats.
@jduthon @didoo
We are changing our case-changing support in 3.0 to use change-case and want to create some unit tests around it. If you have any challenging ones, please submit them on #500