Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: another attempt at cjson.as_array metatable #6

Closed
wants to merge 2 commits into from

Conversation

thibaultcha
Copy link
Member

Hey there!

I would really like to see this being pushed forward, so here is my attempt at convincing of the use of such a patch :) Criticism welcome of course. I mean that I will gladly make the necessary changes if something is not appropriate.

First, I would like to point out that the lack of empty table distinction from mpx/lua-cjson has lead to several (if not many) patches trying to solve this. And that I am glad to see this fork already includes the encode_empty_table_as_object, but I would like to emphasize on the fact that this feature is not enough even in fairly common use cases.

As an example, let's consider someone using the log_by_lua_* phase of ngx_lua (maybe along with lua-resty-logger-socket) to serialize the request/response data and send it somewhere to be stored and analyzed later. Such data could have empty objects representing either arrays or objects in the same Lua table (request headers vs form parameters). This is a simple use case where a higher level of granularity is necessary.

I had to deal with this a few month ago, and the aggregating tool enforced some schema validation, which made it impossible to work around without implementing some very ugly hacks, for which I still do not sleep well at night :)


I read the discussion at #1, and while I too believe a lightuserdata would be more efficient and consistent with cjson.null, I also think it would offload the actual work to the user.

While the metatable approach is fairly straightforward:

-- arr and obj might be empty, but never nil
function serialize(arr, obj)
  setmetatable(arr, cjson.as_array)

  local data = {
    foo = arr,
    bar = obj
  }

  return cjson.encode(data)
end

The lightuserdata is less:

function serialize(arr, obj)
  if #arr < 1 then
    arr = cjson.empty_array
  end

  local data = {
    foo = arr,
    bar = obj
  }

  return cjson.encode(data)
end

Note: if you see a better way of dealing with the userdata approach for users, I would be glad to hear about it and update the patch eventually!


Hence, this patch proposes a slightly improved version of #1, taking the given suggestions into account (since the patch was not updated since then):

  • more specific name for the metatable key in the Lua registry.
  • tests cases moved to t/agentzh.t, where cases for
    'encode_empty_table_as_object' are already written. It seems like a
    better place for tests specific to the OpenResty fork's additions.
  • a more complex test case and slightly more concise changes.

Given the number of times such a feature was requested, I believe it would be really great if it could finally be included, if not in mpx/lua-cjson, at least in OpenResty :)

else {
int as_array = 0;
if (lua_getmetatable(l, -1)) {
luaL_getmetatable(l, CJSON_AS_ARRAY_MT);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we avoid using luaL_getmetatable because it requires (attempts of) creating a new Lua string object?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thibaultcha Maybe we could just store the metatable in our own cjson module table with a lightuserdata key to avoid polluting the global Lua registry.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry but I really don't know what you mean here.

Maybe we could just store the metatable in our own cjson module table

I can see this. But then how does one retrieve it for comparison in json_append_data? Do you mean to store it as an upvalue like the configuration? Either as an upvalue or in the registry is the only way I see this doable.

with a lightuserdata key

I can see some syntactic sugar here and I can implement a cjson.empty_array lightuserdata just like cjson.null, but I don't see how storing the metatable with a lightuserdata key allows one to retrieve the said metatable for comparison in json_append_data either. Except as an upvalue/in registry again, but then the lightuserdata key seems irrelevant?

to avoid polluting the global Lua registry

One could argue the Lua registry is slow and propitious to naming conflicts, but isn't it made for that exact purpose of storing Lua values needed by C modules? In practice, naming conflicts are not so much of a thing (and the name is now prefixed), and however slow the registry is, using it will still be way more efficient than performing the hacks currently necessary to encore empty arrays anyways.

Am I missing something here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thibaultcha Now you are using a string key, "CJSON_AS_ARRAY_MT", to index the metatable in the registry table, which is slow since you need to intern that C string upon every invocation of luaL_getmetatable. I am suggesting the following sequence:

lua_pushlightuserdata(L, &some_global_c_stub_variable_in_cjson);
lua_rawget(L, LUA_REGISTRYINDEX);

By using the cjson table, I mean just store the metatable into the cjson module table returned by require "cjson". It might be tricky in a traditional Lua C module as far as I can see. Thus I'm also fine with using the registry table but with a lightuserdata key as demonstrated above.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that makes sense to me indeed, my understanding of your comment was to store the metatable in the cjson module with a lightuserdata key, hence the confusion, because as you are saying, that would be quite tricky to retrieve for the comparison (except as an upvalue).

@thibaultcha
Copy link
Member Author

I have updated the patch taking your comment in consideration, thanks for clarifying! I also attached the lightuserdata cjson.empty_array to cjson, which allows for an alternative to the metatable approach, in case one finds it more convenient in some cases.

Let me know! :)

@@ -75,6 +75,8 @@
#define DEFAULT_DECODE_INVALID_NUMBERS 0
#endif

static const char *json_empty_array = "json_empty_array";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can avoid the array value "json_empty_array" here to save some memory. All we need now is just an address.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Er yeah, it's how I first implemented it, should have left it as is.

@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch 2 times, most recently from 12403c7 to 78558da Compare February 29, 2016 20:28
@agentzh
Copy link
Member

agentzh commented Mar 1, 2016

@thibaultcha One last thing: will you please drop a quick note to README.md (I'm aware that this file does not even exist atm)? This way people can know how to use things without checking out source or PRs. Thanks for your time!

@thibaultcha
Copy link
Member Author

Absolutely, I actually intended to ask you if we wanted to document this and where once the patch confirmed!

@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch 2 times, most recently from 05c2c3f to 78c4a55 Compare March 1, 2016 05:43
@thibaultcha
Copy link
Member Author

I just added a README following more or less the OpenResty layout, let me know what you think of it.

@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch 2 times, most recently from b27cced to 67592c8 Compare March 1, 2016 05:46
@agentzh
Copy link
Member

agentzh commented Mar 1, 2016

@thibaultcha Wow. The README looks awesome. Thanks for your contribution! I'll try testing your patches on my side and merging the stuff tonight.

@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch from 67592c8 to 0312456 Compare March 1, 2016 23:13
A proposed improved patch of openresty#1 (a patch commonly
proposed to lua-cjson and its forks), taking into considerations
comments from the original PR.

- use a lightuserdata key to store the metatable in the Lua Registry
  (more efficient and avoiding conflicts)
- provide a lightuserdata resulting in empty arrays as well
- tests cases moved to t/agentzh.t, where cases for
  'encode_empty_table_as_object' are already written. It seems like a
  better place for tests specific to the OpenResty fork's additions.
- a more complex test case
@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch from 0312456 to efcffec Compare March 3, 2016 18:55
@thibaultcha
Copy link
Member Author

Glad you like it! I just added a test case to ensure non-empty Lua tables with the empty_array_mt metatable are not encoded as empty arrays.

@thibaultcha thibaultcha force-pushed the feature/as-array-mt branch from efcffec to 133adf7 Compare March 7, 2016 08:33
@thibaultcha
Copy link
Member Author

Hey @agentzh, did you get any chance to give this a try? I just feel like a release is coming and I think it'd be great to include those new features! I could be wrong though :)

@agentzh
Copy link
Member

agentzh commented Mar 9, 2016

@thibaultcha Been busy preparing tomorrow's openresty meetup. Sorry for the delay on my side. Will look into this as soon as I can manage.

@thibaultcha
Copy link
Member Author

Yeah I bet. Excited! Ok :)

@doujiang24
Copy link
Member

@thibaultcha Very Nice:)

@agentzh
Copy link
Member

agentzh commented Mar 14, 2016

@thibaultcha Merged with a minor file name extension change. Thanks!

@agentzh agentzh closed this Mar 14, 2016
@thibaultcha
Copy link
Member Author

Great day for OpenResty! 😁 Thank you!

thibaultcha added a commit to Kong/kong that referenced this pull request Aug 3, 2016
Now that openresty/lua-cjson#6 has been merged
and OpenResty has been bumped to 1.9.15.1, we can use more granular
empty array encoding for ALF serialization.
thibaultcha added a commit to Kong/kong that referenced this pull request Aug 6, 2016
Now that openresty/lua-cjson#6 has been merged
and OpenResty has been bumped to 1.9.15.1, we can use more granular
empty array encoding for ALF serialization.
hishamhm pushed a commit to hishamhm/lua-cjson that referenced this pull request Jul 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants