{"id":1495,"date":"2023-05-24T12:27:03","date_gmt":"2023-05-24T11:27:03","guid":{"rendered":"https:\/\/marketaylor.synology.me\/?p=1495"},"modified":"2023-05-24T12:30:54","modified_gmt":"2023-05-24T11:30:54","slug":"node-js-c-add-on-performance","status":"publish","type":"post","link":"https:\/\/marketaylor.synology.me\/?p=1495","title":{"rendered":"Node.js Add-ons:  Object transformation performance"},"content":{"rendered":"\n<p>While working on the redesigned Node.js C++ <a href=\"https:\/\/marketaylor.synology.me\/?p=1482\" target=\"_blank\" rel=\"noreferrer noopener\">add-on for MQ<\/a>, I had a question about transforming objects. What was the best method for Node.js performance?  Noone answered the question on the internal channels I tried. I also couldn&#8217;t find anything definitive on external documentation or blogs. So I wrote my own tests &#8230;<\/p>\n\n\n\n<!--more-->\n\n\n\n<h3 class=\"wp-block-heading\">The problem<\/h3>\n\n\n\n<p>The <a href=\"https:\/\/github.com\/nodejs\/node-addon-api\/tree\/main\" target=\"_blank\" rel=\"noreferrer noopener\">Node-API <\/a>interface (often known as N-API) provides a way for Node.js applications to call &#8220;native&#8221; services. A C or C++ layer takes responsibility for marshalling parameters and calling the external API. In many cases, that external API is a C or C++ library. For my project, I needed to call IBM MQ functions that have a C API.<\/p>\n\n\n\n<p>The Node application has objects containing the various parameters for the external API, and we need to convert them to the correct C formats.<\/p>\n\n\n\n<p>As an example, imagine that the C API has a single parameter, a pointer to a structure:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">struct s {\n  int f1;\n  char f2[32];\n}<\/pre>\n\n\n\n<p>The JS application sets the values in a simple object analogue:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">o = {f1:42, f2:\"text\"};<\/pre>\n\n\n\n<p>Something needs to take the fields from the JS object and set appropriate values in the C structure. What&#8217;s the best approach?<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Options<\/h3>\n\n\n\n<p>There are two &#8220;obvious&#8221; ways to do this:<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Conversion in the C++ layer<\/h4>\n\n\n\n<p>The C++ code is passed the JS object directly and extracts the field values invidually. Ignoring error handling, the C++ code would look something like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">using namespace Napi;\nvoid Transformer(const CallbackInfo &amp;info) {\n  struct s s;\n  Object o = info[0].As&lt;Object>();\n\n  s.f1 = o.Get(\"f1\").As&lt;Number>().Int32Value();\n  strncpy(s.f2,o.Get(\"f2\").As&lt;String>().Utf8Value().c_str(),sizeof(s.f2));\n\n  REALAPI(&amp;s);\n}\n<\/pre>\n\n\n\n<p>This seems to be the primary approach used in the <a href=\"https:\/\/github.com\/nodejs\/node-addon-examples\" target=\"_blank\" rel=\"noreferrer noopener\">add-on examples<\/a>. Though that might be simply because it&#8217;s a good idea to show examples of using the API.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Conversion in the Node.JS layer<\/h4>\n\n\n\n<p>The JS layer builds a byte buffer that is passed through the C++ layer. The buffer matches exactly the layout of the structure. We may have to worry about string conversion though, and this example assumes little-endian integer layouts.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">b = Buffer.alloc(36); \/\/ sizeof(C structure)\nb.WriteInt32LE(o.f1,4);\nfor (i=0;i&lt;o.f2.length;i++) {\n  b[4+i] = o.f2.charCodeAt(i);\n}\naddon.REALAPI(b);<\/pre>\n\n\n\n<p>The corresponding C++ layer can then pass the buffer on without any real work:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void Transformer(const CallbackInfo &amp;info) {\n  Buffer&lt;unsigned char&gt; b = info[0].As&lt;Buffer&lt;unsigned char&gt;&gt;();\n  REALAPI(b.Data());\n}<\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Other approaches<\/h4>\n\n\n\n<p>For this trivial structure, a better answer might be to simply pass the 2 fields as separate parameters. But the real-world structures I&#8217;m dealing with have many more fields which make that impractical.<\/p>\n\n\n\n<p>There are some external npm-provided packages that can do this conversion for you. You just describe the C structure using a JS syntax. But for various reasons &#8211; primarily because those do not work on all the platforms I&#8217;m interested in &#8211; those packages could not be used.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Performance Testing<\/h3>\n\n\n\n<p>It&#8217;s not immediately obvious which route performs better. In both situations the engine code has to lookup the field name in some kind of internal map for the object, and convert the element to a native format. I would not have been surprised to find them both being essentially the same.<\/p>\n\n\n\n<p>But as I couldn&#8217;t find a definitive documented answer to the question, I ended up writing my own tests using the MQMD structure which has about 30 fields of different types (mostly integer and pseudo-string).<\/p>\n\n\n\n<p>For the JS conversion option, I calculated the offsets of each field at initialisation time. The <code>bufNew...<\/code>functions update a local map with the offset for the field.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">md = Buffer.alloc(MQC.MQMD_LENGTH_2);\no = {offset: 0};\nmdOffsets = {};\n\nu.bufNewWriteString(md, mdOffsets, \"StrucId\", \"MD  \", 4, o);\nu.bufNewWriteInt32(md, mdOffsets, \"Version\", MQC.MQMD_VERSION_2, o);\nu.bufNewWriteInt32(md, mdOffsets, \"Report\", MQC.MQRO_NONE, o);\n...<\/pre>\n\n\n\n<p>with the individual fields then converted when needed:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">u.bufWriteInt32(mqmd, jsmd.MsgType, mdOffsets.MsgType);\nu.bufWriteInt32(mqmd, jsmd.Expiry, mdOffsets.Expiry);<\/pre>\n\n\n\n<p>Similar code handled the reverse transform, reading from a returned Buffer to populate a new JS object. The private  <code>u.<\/code>utility package for these tests handles big\/little-endian conversions, and the string processing. And the <code>mdOffsets<\/code> map tells those functions where to set or read the elements so they don&#8217;t have to be listed in exactly the same order as in the C structure.<\/p>\n\n\n\n<p>The C++ code just calls <code>Object.Get(field)<\/code> for each field, with assignments or <code>strncpy<\/code> as necessary. For the reverse path, it was something like <code>Object.Set(field, Napi::Number::New(env, value))<\/code>. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Results<\/h4>\n\n\n\n<p>The results were surprisingly clear. I ran the tests several times on a few different platforms. Conversions happened in both directions about 50K times. These were not necessarily the fastest systems, but the difference was significant enough, and consistent enough, that I was happy to accept the answer:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Elapsed [JS]  = 580ms\nElapsed [C++] = 1608ms\n<strong>Ratio =  2.77<\/strong><\/pre>\n\n\n\n<p>Building and passing a single byte buffer in the Node layer is a lot faster than building it in the addon C++ code.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Other considerations<\/h3>\n\n\n\n<p>The Node layer does not understand anything about C structure layouts and how they might be physically laid out &#8211; padding and alignment rules for example. For the MQ structures, that has always been a consideration in the original design. Some structures have explicit &#8220;reserved&#8221; fields, to make sure of word alignment where required. So I didn&#8217;t need to worry too much about that.<\/p>\n\n\n\n<p>A second thing to think about is if the C structure contains pointer values. Often those might be pointers to other structures or elements from other API parameters. The best thing there is likely to be to leave the pointer values empty in the JS conversion, but then set them in the C++ layer. So the conversion is then a hybrid model:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void Transformer(const CallbackInfo &amp;info) {\n  struct s { int f1;char f2[32]; void *f3; } *ps;\n  \n  Buffer&lt;unsigned char&gt; b0 = info[0].As&lt;Buffer&lt;unsigned char&gt;&gt;();\n  Buffer&lt;unsigned char&gt; b1 = info[1].As&lt;Buffer&lt;unsigned char&gt;&gt;();\n  ps = b0.Data();\n  ps-&gt;f3 = b1.Data();\n\n  REALAPI(ps);\n}<\/pre>\n\n\n\n<p>Both of the primary methods have a similar amount of code, with similar levels of complexity. At some point, one or other of the sides have to know about all of the fields by name and by type in the structure to do the copying. So performance is the main factor influencing the design. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Conclusion<\/h3>\n\n\n\n<p>It&#8217;s clear that doing transformation in the JS layer is the better-performing option. But I ended up using both models for different structures. Those that I expect to be used &#8220;rarely&#8221; get handled with the C++ transformer. Conveniently, these are also the more complicated structures, with pointers and elements that may have to be copied into temporary buffers to avoid problems that may occur with garbage collection kicking in and deleting the JS objects. I can better manage any malloc\/free calls when it&#8217;s all in a single place.<\/p>\n\n\n\n<p>The heavily-used structures &#8211; for MQ, those are the ones that are associated with every PUT or GET of a message &#8211; are instead being managed in the JS transformer. <\/p>\n\n\n\n<p>I hope that anyone writing their own addon modules finds this useful.<\/p>\n<p class=\"last-modified\" style=\"border:1px solid;padding: 10px;\">This post was last updated on May 24th, 2023 at 12:30 pm<\/p>","protected":false},"excerpt":{"rendered":"<p>While working on the redesigned Node.js C++ add-on for MQ, I had a question about transforming objects. What was the best method for Node.js performance? Noone answered the question on the internal channels I tried. I also couldn&#8217;t find anything definitive on external documentation or blogs. So I wrote my own tests &#8230; This post &hellip; <a href=\"https:\/\/marketaylor.synology.me\/?p=1495\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Node.js Add-ons:  Object transformation performance&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":510,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[63,129],"class_list":["post-1495","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-mq","tag-nodejs","tag-performance"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog<\/title>\n<meta name=\"description\" content=\"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/marketaylor.synology.me\/?p=1495\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog\" \/>\n<meta property=\"og:description\" content=\"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?\" \/>\n<meta property=\"og:url\" content=\"https:\/\/marketaylor.synology.me\/?p=1495\" \/>\n<meta property=\"og:site_name\" content=\"Mark Taylor&#039;s Blog\" \/>\n<meta property=\"article:published_time\" content=\"2023-05-24T11:27:03+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-05-24T11:30:54+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png\" \/>\n\t<meta property=\"og:image:width\" content=\"256\" \/>\n\t<meta property=\"og:image:height\" content=\"156\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Mark\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@marketaylor\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Mark\" \/>\n\t<meta name=\"twitter:label2\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495\"},\"author\":{\"name\":\"Mark\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/#\\\/schema\\\/person\\\/2d6f4113ff54187023e20c20186bbb3c\"},\"headline\":\"Node.js Add-ons: Object transformation performance\",\"datePublished\":\"2023-05-24T11:27:03+00:00\",\"dateModified\":\"2023-05-24T11:30:54+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495\"},\"wordCount\":1007,\"commentCount\":1,\"image\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/marketaylor.synology.me\\\/wp-content\\\/uploads\\\/Pictures\\\/2019\\\/11\\\/nodejs-logo.png\",\"keywords\":[\"nodejs\",\"performance\"],\"articleSection\":[\"IBM MQ\"],\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495\",\"url\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495\",\"name\":\"Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/marketaylor.synology.me\\\/wp-content\\\/uploads\\\/Pictures\\\/2019\\\/11\\\/nodejs-logo.png\",\"datePublished\":\"2023-05-24T11:27:03+00:00\",\"dateModified\":\"2023-05-24T11:30:54+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/#\\\/schema\\\/person\\\/2d6f4113ff54187023e20c20186bbb3c\"},\"description\":\"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#primaryimage\",\"url\":\"https:\\\/\\\/marketaylor.synology.me\\\/wp-content\\\/uploads\\\/Pictures\\\/2019\\\/11\\\/nodejs-logo.png\",\"contentUrl\":\"https:\\\/\\\/marketaylor.synology.me\\\/wp-content\\\/uploads\\\/Pictures\\\/2019\\\/11\\\/nodejs-logo.png\",\"width\":256,\"height\":156},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/?p=1495#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/marketaylor.synology.me\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Node.js Add-ons: Object transformation performance\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/#website\",\"url\":\"https:\\\/\\\/marketaylor.synology.me\\\/\",\"name\":\"Mark Taylor&#039;s Blog\",\"description\":\"Messaging, Music and Moving Around\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/marketaylor.synology.me\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/marketaylor.synology.me\\\/#\\\/schema\\\/person\\\/2d6f4113ff54187023e20c20186bbb3c\",\"name\":\"Mark\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g\",\"caption\":\"Mark\"},\"sameAs\":[\"https:\\\/\\\/x.com\\\/marketaylor\"],\"url\":\"https:\\\/\\\/marketaylor.synology.me\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog","description":"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/marketaylor.synology.me\/?p=1495","og_locale":"en_GB","og_type":"article","og_title":"Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog","og_description":"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?","og_url":"https:\/\/marketaylor.synology.me\/?p=1495","og_site_name":"Mark Taylor&#039;s Blog","article_published_time":"2023-05-24T11:27:03+00:00","article_modified_time":"2023-05-24T11:30:54+00:00","og_image":[{"width":256,"height":156,"url":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","type":"image\/png"}],"author":"Mark","twitter_card":"summary_large_image","twitter_creator":"@marketaylor","twitter_misc":{"Written by":"Mark","Estimated reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/marketaylor.synology.me\/?p=1495#article","isPartOf":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495"},"author":{"name":"Mark","@id":"https:\/\/marketaylor.synology.me\/#\/schema\/person\/2d6f4113ff54187023e20c20186bbb3c"},"headline":"Node.js Add-ons: Object transformation performance","datePublished":"2023-05-24T11:27:03+00:00","dateModified":"2023-05-24T11:30:54+00:00","mainEntityOfPage":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495"},"wordCount":1007,"commentCount":1,"image":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495#primaryimage"},"thumbnailUrl":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","keywords":["nodejs","performance"],"articleSection":["IBM MQ"],"inLanguage":"en-GB","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/marketaylor.synology.me\/?p=1495#respond"]}]},{"@type":"WebPage","@id":"https:\/\/marketaylor.synology.me\/?p=1495","url":"https:\/\/marketaylor.synology.me\/?p=1495","name":"Node.js Add-ons: Object transformation performance - Mark Taylor&#039;s Blog","isPartOf":{"@id":"https:\/\/marketaylor.synology.me\/#website"},"primaryImageOfPage":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495#primaryimage"},"image":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495#primaryimage"},"thumbnailUrl":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","datePublished":"2023-05-24T11:27:03+00:00","dateModified":"2023-05-24T11:30:54+00:00","author":{"@id":"https:\/\/marketaylor.synology.me\/#\/schema\/person\/2d6f4113ff54187023e20c20186bbb3c"},"description":"A Node.js C++ add-on needs to transform objects between native and JS formats. What is best for Node.JS performance?","breadcrumb":{"@id":"https:\/\/marketaylor.synology.me\/?p=1495#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/marketaylor.synology.me\/?p=1495"]}]},{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/marketaylor.synology.me\/?p=1495#primaryimage","url":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","contentUrl":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","width":256,"height":156},{"@type":"BreadcrumbList","@id":"https:\/\/marketaylor.synology.me\/?p=1495#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/marketaylor.synology.me\/"},{"@type":"ListItem","position":2,"name":"Node.js Add-ons: Object transformation performance"}]},{"@type":"WebSite","@id":"https:\/\/marketaylor.synology.me\/#website","url":"https:\/\/marketaylor.synology.me\/","name":"Mark Taylor&#039;s Blog","description":"Messaging, Music and Moving Around","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/marketaylor.synology.me\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Person","@id":"https:\/\/marketaylor.synology.me\/#\/schema\/person\/2d6f4113ff54187023e20c20186bbb3c","name":"Mark","image":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/secure.gravatar.com\/avatar\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/9a5ae091c43730194cba7cabb5d65c1dc3f48d05caaddec6ff2319a1ce66376f?s=96&d=mm&r=g","caption":"Mark"},"sameAs":["https:\/\/x.com\/marketaylor"],"url":"https:\/\/marketaylor.synology.me\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/marketaylor.synology.me\/wp-content\/uploads\/Pictures\/2019\/11\/nodejs-logo.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/posts\/1495","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1495"}],"version-history":[{"count":8,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/posts\/1495\/revisions"}],"predecessor-version":[{"id":1504,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/posts\/1495\/revisions\/1504"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=\/wp\/v2\/media\/510"}],"wp:attachment":[{"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1495"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1495"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/marketaylor.synology.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1495"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}