{"id":54,"date":"2015-05-12T11:54:10","date_gmt":"2015-05-12T18:54:10","guid":{"rendered":"https:\/\/www.alexkorn.com\/blog\/?p=54"},"modified":"2015-05-24T23:50:05","modified_gmt":"2015-05-25T06:50:05","slug":"hand-rolling-valid-json-in-mysql-using-group_concat","status":"publish","type":"post","link":"https:\/\/www.alexkorn.com\/blog\/2015\/05\/hand-rolling-valid-json-in-mysql-using-group_concat\/","title":{"rendered":"Hand-rolling valid JSON in MySQL using GROUP_CONCAT"},"content":{"rendered":"<h3>The problem<\/h3>\n<p>\nRecently, I came across what started as a straightforward problem: In MySQL I was JOINing from Table A to Table B on a&#8217;s primary key, and wanted to display a list of all items in Table A, each followed by a list of all associated items in Table B.\n<\/p>\n<p>\nMy initial thought was to hand-roll my own JSON, so that the result of the query would have a few columns with the results of Table A, then a column with the JSON-encoded data from Table B. I realize that one should never hand-roll their own JSON, but there isn&#8217;t a native JSON encoder in MySQL, so I had to make do.\n<\/p>\n<p>\nIt was easy enough to come up with a basic JSON format using the following. I&#8217;ve done some pretty aggressive tabbing and newlines here to try to make the layers of functions as straightforward as possible; different arguments to each function align vertically.\n<\/p>\n<pre lang=\"mysql\">\r\nSELECT a.aId, a.name,\r\n    CONCAT(\r\n        '[',\r\n        GROUP_CONCAT(\r\n            CONCAT(\r\n                '[',\r\n                b.bId, ', ',\r\n                '\\\"', b.name, '\\\"',\r\n                ']')\r\n            ORDER BY b.bId ASC\r\n            SEPARATOR ','),\r\n        ']'\r\n    ) AS bData\r\nFROM a\r\n    JOIN b\r\n        ON a.bId = b.bId\r\nGROUP BY a.aId\r\n<\/pre>\n<p>\n(<a href=\"http:\/\/stackoverflow.com\/questions\/12511933\/how-create-json-format-with-group-concat-mysql\">Note that I am not the first person to come up with this solution.<\/a>)\n<\/p>\n<p>\nHowever, if <code>b.name<\/code> has a quotation mark (or a variety of other characters), this creates invalid JSON. How do we ensure that we always create valid JSON, even when there are special characters or unicode\/multi-byte characters?\n<\/p>\n<h3>The solution<\/h3>\n<p>\nEnsuring we always have valid JSON was surprisingly easy &#8211; we can <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.5\/en\/string-functions.html#function_hex\">hexadecimal-encode<\/a> <code>b.name<\/code> in the MySQL query, which ensures that its character set is 0-9 or A-F, so it will always be properly contained within the quotation marks around it. We then convert it back into a normal string in the application code.\n<\/p>\n<pre lang=\"mysql\">\r\nSELECT a.aId, a.name,\r\n    CONCAT(\r\n        '[',\r\n        COALESCE(\r\n            GROUP_CONCAT(\r\n                CONCAT(\r\n                    '[',\r\n                    b.bId, ', ',\r\n                    '\\\"', HEX(b.name), '\\\"',\r\n                    ']')\r\n                ORDER BY b.bId ASC\r\n                SEPARATOR ','),\r\n            ''),\r\n        ']') AS bData\r\nFROM a\r\n    JOIN b\r\n        ON a.bId = b.bId\r\nGROUP BY a.aId\r\n<\/pre>\n<p>\nSo let&#8217;s say <code>b.name<\/code> was something like <code>'\u00e2\u02dc\u0192'<\/code> (because I know people are always naming their things [unicode Snowman]), the resulting hexadecimal representation is <code>27E2988327<\/code>. Opaque, but definitely JSON-safe!\n<\/p>\n<p>\nNote that we also use <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/5.5\/en\/comparison-operators.html#function_coalesce\">COALESCE<\/a> in case there are no associated items in Table B; otherwise <code>CONCAT('[', NULL, ']'<\/code> gives us <code>NULL<\/code>, not <code>[]<\/code>.\n<\/p>\n<p>\nAll we have left to do is convert the hexadecimal representation back into text in the application. Here&#8217;s a function in PHP that does the trick:\n<\/p>\n<pre lang=\"php\">\r\nfunction hexToStr($hex)\r\n{\r\n    $string = '';\r\n    for ($charIter = 0; $charIter < strlen($hex) - 1; $charIter += 2)\r\n    {\r\n        $string .= chr(hexdec($hex[$charIter] . $hex[$charIter + 1]));\r\n    }\r\n    return $string;\r\n}\r\n<\/pre>\n<p>\nHappy coding, and remember to be safe and always properly escape your strings!\n<\/p>\n<p>\nFeel free to ask questions or give feedback <a href=\"https:\/\/twitter.com\/alexkorn\">via Twitter<\/a>.<\/p>\n<!-- AddThis Advanced Settings generic via filter on the_content --><!-- AddThis Share Buttons generic via filter on the_content -->","protected":false},"excerpt":{"rendered":"<p>The problem Recently, I came across what started as a straightforward problem: In MySQL I was JOINing from Table A to Table B on a&#8217;s primary key, and wanted to display a list of all items in Table A, each followed by a list of all associated items in Table B. My initial thought was [&hellip;]<!-- AddThis Advanced Settings generic via filter on get_the_excerpt --><!-- AddThis Share Buttons generic via filter on get_the_excerpt --><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[32,34,14,6,33,35],"class_list":["post-54","post","type-post","status-publish","format-standard","hentry","category-tutorials","tag-json","tag-multi-byte-safe","tag-mysql","tag-php","tag-string-escaping","tag-unicode"],"_links":{"self":[{"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/posts\/54","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/comments?post=54"}],"version-history":[{"count":0,"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/posts\/54\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/media?parent=54"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/categories?post=54"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.alexkorn.com\/blog\/wp-json\/wp\/v2\/tags?post=54"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}