{"id":2155,"date":"2013-11-15T08:02:53","date_gmt":"2013-11-14T20:02:53","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=2155"},"modified":"2013-11-15T08:12:20","modified_gmt":"2013-11-14T20:12:20","slug":"bonjour-json","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/2155\/","title":{"rendered":"Bonjour JSON"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>I know, you wait 5 years for a library then three come along at once!  As well as <strong>Smoketest<\/strong> I also want to mention a couple of other libraries that have been published alongside it.  They are wholly unrelated to Smoketest itself, so I decided to just quickly mention them in this separate post.<\/p>\n<p><!--more--><\/p>\n<p>The first thing to mention is that as with all my code, these libraries support <strong>all<\/strong> versions of Delphi back to <strong>Delphi 7<\/strong>.  It is only tested on Win32 VCL platforms (and Win64 where supported).<\/p>\n<h2>JSON<\/h2>\n<p>The first is a <strong>JSON<\/strong> implementation and parser that I wrote a long, long time ago though it has been updated a little recently.  This is incorporated in <a href=\"https:\/\/github.com\/deltics\/delphi.libs\/blob\/master\/rtl\/Deltics.JSON.pas\" target=\"_blank\">a single unit in the <code class=\"\" data-line=\"\">rtl<\/code> library of the <code class=\"\" data-line=\"\">delphi.libs<\/code> project<\/a> that also contains <strong>Smoketest<\/strong>.<\/p>\n<p>One thing to note about this implementation is that all non-ASCII values are emitted as <strong>escaped Unicode<\/strong> (<code class=\"\" data-line=\"\">\\Uxxxx<\/code>).<\/p>\n<p>Duplicate value names are supported, taking a li<del>b<\/del>teral approach to the interpretation of the specification (<a href=\"http:\/\/www.ietf.org\/rfc\/rfc4627.txt\">RFC-4267<\/a>).  The specification states that duplicate names <strong>SHOULD<\/strong> be supported but does not stipulate <strong>how<\/strong>.<\/p>\n<p>My JSON parser supports duplicate values currently by simply creating a JSON structure with all named values.  Where duplicate names exist in a JSON structure only the first will be found if accessed by name, so given the JSON data:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n  {\r\n    &quot;duplicate&quot;: &quot;first&quot;,\r\n    &quot;duplicate&quot;: &quot;second&quot;\r\n  }\r\n<\/pre>\n<p>Accessing the &#8220;<strong>duplicate<\/strong>&#8221; value by name (the default property of a JSON object):<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  str := json&#x5B;'duplicated'].AsString;  \/\/ returns 'first'\r\n<\/pre>\n<p>But <strong>all<\/strong> occurrences of a particular named value may be accessed by ordinal index just as with any other value:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  str := json.ValueByIndex&#x5B;0].AsString;  \/\/ returns 'first'\r\n  str := json.ValueByIndex&#x5B;1].AsString;  \/\/ returns 'second'\r\n<\/pre>\n<p>Similarly, if you explicitly add two values with the same name then that is what the JSON object will emit.  If you change a value by name then you will replace the value of the first such named value, but again you can still change any value by ordinal index:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  json.Add('duplicate', 'first');\r\n  json.Add('duplicate', 'second');\r\n\r\n  json&#x5B;'duplicate'].AsString    := 'new first';\r\n  json.ValueByIndex&#x5B;1].AsString := 'new second';\r\n\r\n\r\n\/\/ result:\r\n\/\/\r\n\/\/   {\r\n\/\/    &quot;duplicate&quot;: &quot;new first&quot;,\r\n\/\/    &quot;duplicate&quot;: &quot;new second&quot;\r\n\/\/   }\r\n<\/pre>\n<p>In effect this means that the JSON object itself makes no decision about how to treat duplicate names where they exist, leaving it for the consumer code to make this decision and behave accordingly.<\/p>\n<p>On that note I would add that the ambiguity of the specification in this area is a well known problem and I strongly recommend that in any JSON implementation you avoid any potential problems by simply not using duplicated value names at all.  But if you are presented with JSON that does, at least this implementation allows you to choose how to work with them.<\/p>\n<p>I am currently contemplating providing a mechanism to specify how the parser should treat duplicate names so that application code can delegate any particular implementation decision in this area to the JSON object.<\/p>\n<p>A number of non-JSON data types are supporting on the API (GUID, TDateTime, enums etc), but these all resolve down to JSON core types underneath &#8211; usually <strong>string<\/strong> &#8211; and are simply provided as a convenience when using JSON as an interchange data structure being passed between two Delphi authored processes.<\/p>\n<h2>BONJOUR<\/h2>\n<p>The second is a far more sophisticated affair:  A set of components to support the <a href=\"https:\/\/developer.apple.com\/bonjour\/\">Apple Bonjour Service Discovery API<\/a>.<\/p>\n<p>This is also in the <code class=\"\" data-line=\"\">delphi.libs<\/code> project but is <a href=\"https:\/\/github.com\/deltics\/delphi.libs\/tree\/master\/bonjour\" target=\"_blank\">a library in it&#8217;s own right<\/a>.<\/p>\n<p>The components are intended primarily for the very simple advertisement and discovery of services, rather than being a complete <strong>DNS-SD<\/strong> framework (whatever that means &#8211; something I am still learning on an as-needed basis).<\/p>\n<p>To advertise a service you use a <strong>TService<\/strong> component, simply set the service <strong>name<\/strong>, <strong>type<\/strong> and <strong>port<\/strong> and set it active.  So for example, to advertise a (fake) <strong>iTunes shared library<\/strong>:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  service := TService.Create(self);\r\n  service.ServiceName := 'A Fake iTunes Library';\r\n  service.ServiceType := '_daap._tcp';  \/\/ the service type of an iTunes library\r\n  service.Port        := 3572;\r\n  service.Active      := TRUE;\r\n<\/pre>\n<p>You can also add <strong>TXT<\/strong> records for your advertised service if you need to.<\/p>\n<p>Discovering services is a little bit more involved, though not much.  For this you use a <strong>TListener<\/strong>.  Set the service type you are interested in, assign a handler to respond to newly discovered services (and services that disappear, if you need to).  When you discover a service you typically resolve it (in your <strong>ServiceFound<\/strong> handler) to obtain further information about the service so you will need a handler for the event that resolving a service will trigger.  Then set the listener active:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  procedure TMyForm.DoServiceFound(const aSender: TListener; \r\n                                   const aService: TServiceInfo);\r\n  begin\r\n    aService.Resolve;\r\n  end;\r\n\r\n\r\n  procedure TMyForm.DoServiceResolved(const aSender: TListener;\r\n                                      const aService: TServiceInfo);\r\n  begin\r\n    \/\/ service hostname, port and TXT records are now available...\r\n  end;\r\n\r\n\r\n  listener := TListener.Create(self);\r\n  listener.ServiceType       := '_daap._tcp';\r\n  listener.OnServiceFound    := DoServiceFound;\r\n  listener.OnServiceResolved := DoServiceResolved;\r\n  listener.Active            := TRUE;\r\n<\/pre>\n<p>Alternatively you can resolve a service synchronously by specifying a timeout (in milliseconds) to the <code class=\"\" data-line=\"\">Resolve<\/code> method, avoiding the need for a second event handler.  In this case, the <code class=\"\" data-line=\"\">Resolve<\/code> method returns a <strong>boolean<\/strong> indicating whether the service was resolved within the time allowed (if it didn&#8217;t it may still eventually resolve):<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  procedure TMyForm.DoServiceFound(const aSender: TListener; \r\n                                   const aService: TServiceInfo);\r\n  begin\r\n    if aService.Resolve(1000) then\r\n      \/\/ service resolved - host, port and TXT etc now available \r\n  end;\r\n<\/pre>\n<p>I hope to find time to blog more about these libraries in the coming weeks, but if you&#8217;re prepared to get your hands filthy and learn the hard way (not least by finding my bugs for me &#8211; thanks Stefan! :)) then the code is already up in <a href=\"https:\/\/github.com\/deltics\/delphi.libs\">the same github repository as Smoketest<\/a>.<\/p>\n<p>Both libraries have <strong>Smoketest<\/strong> test projects.<\/p>\n<p>These are far from complete and are frankly more than a little scruffy, but should provide some initial guidance on how to use these libraries, if you are interested.<\/p>\n<p>Once again, both of these libraries support <strong>all<\/strong> versions of Delphi back to <strong>Delphi 7<\/strong>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\"> 4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>I know, you wait 5 years for a library then three come along at once! As well as Smoketest I also want to mention a couple of other libraries that have been published alongside it. They are wholly unrelated to Smoketest itself, so I decided to just quickly mention them in this separate post.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[4],"tags":[259,292,260],"class_list":["post-2155","post","type-post","status-publish","format-standard","hentry","category-delphi","tag-bonjour","tag-delphi","tag-json"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-yL","jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2155","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=2155"}],"version-history":[{"count":7,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2155\/revisions"}],"predecessor-version":[{"id":2161,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2155\/revisions\/2161"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=2155"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=2155"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=2155"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}