Our Partner & Developer boards on the community are moving to a brand new home: the .dev community forums! While you can still access past discussions here, for all your future app and storefront building questions, head over to the new forums.

Rich Text metafield JSON input via GraphQL

Solved

Rich Text metafield JSON input via GraphQL

tinyman1199
Shopify Partner
9 1 3

Hi All,

 

I was looking to use GraphQL to update a rich text metafield, however found that the metafield required JSON but not in a format converted directly from HTML.

I looked for a solution but failed to find one, I implemented my own solution in PHP and thought I'd share here in case anyone else needs it or could improve my code.

 

An example of the JSON required by Shopify:

 

 

{
  "type": "root",
  "children": [
    {
      "type": "heading",
      "children": [
        {
          "type": "text",
          "value": "Heading"
        }
      ],
      "level": 1
    },
    {
      "type": "paragraph",
      "children": [
        {
          "type": "text",
          "value": "bold ",
          "bold": true
        },
        {
          "type": "text",
          "value": "normal text "
        },
        {
          "type": "text",
          "value": "italics",
          "italic": true
        }
      ]
    },
    {
      "type": "paragraph",
      "children": [
        {
          "type": "text",
          "value": ""
        },
        {
          "url": "link url",
          "title": "link title",
          "type": "link",
          "children": [
            {
              "type": "text",
              "value": "link",
              "italic": true
            }
          ]
        },
        {
          "type": "text",
          "value": ""
        }
      ]
    },
    {
      "listType": "unordered",
      "type": "list",
      "children": [
        {
          "type": "list-item",
          "children": [
            {
              "type": "text",
              "value": "unordered list 1"
            }
          ]
        },
        {
          "type": "list-item",
          "children": [
            {
              "type": "text",
              "value": "unordered list 2"
            }
          ]
        }
      ]
    },
    {
      "listType": "ordered",
      "type": "list",
      "children": [
        {
          "type": "list-item",
          "children": [
            {
              "type": "text",
              "value": "list item 1"
            }
          ]
        },
        {
          "type": "list-item",
          "children": [
            {
              "type": "text",
              "value": "list item 2"
            }
          ]
        }
      ]
    }
  ]
}

 

 

Accepted Solution (1)

tinyman1199
Shopify Partner
9 1 3

This is an accepted solution.

Here is my solution where you pass the html required for your rich text to html_to_obj and then get the first child within the returned object.

function html_to_obj($html)
{
  $dom = new DOMDocument();
  $dom->loadHTML($html);
  return element_to_obj($dom->documentElement);
}

function element_to_obj($element)
{
  $loop = true;
  if ($element->tagName == "body") {
    $obj = array("type" => "root");
  } else {
    if (str_starts_with($element->tagName, "h2")) {
      $obj = array("type" => "heading", "level" => 2);
    } elseif (str_starts_with($element->tagName, "h3")) {
      $obj = array("type" => "heading", "level" => 3);
    } elseif (str_starts_with($element->tagName, "p")) {
      $obj = array("type" => "paragraph");
    } elseif (str_starts_with($element->tagName, "a")) {
      $obj = array("type" => "link");
    } elseif (str_starts_with($element->tagName, "ol")) {
      $obj = array("type" => "list", "listType" => "ordered");
    } elseif (str_starts_with($element->tagName, "ul")) {
      $obj = array("type" => "list", "listType" => "unordered");
    } elseif (str_starts_with($element->tagName, "li")) {
      $obj = array("type" => "list-item");
    } elseif (str_starts_with($element->tagName, "strong")) {
      $obj = array("type" => "text", "bold" => true, "value" => $element->childNodes[0]->wholeText);
      $loop = false;
    } elseif (str_starts_with($element->tagName, "em")) {
      $obj = array("type" => "text", "italic" => true, "value" => $element->childNodes[0]->wholeText);
      $loop = false;
    }
  }
  if ($loop == true) {
    foreach ($element->attributes as $attribute) {
      if ($attribute->name == "href") {
        $obj["url"] = $attribute->value;
      } else {
        $obj[$attribute->name] = $attribute->value;
      }
    }
    foreach ($element->childNodes as $subElement) {
      echo $subElement->nodeType;
      if ($subElement->nodeType == XML_TEXT_NODE) {
        $obj["children"][] =  array("type" => "text", "value" => $subElement->wholeText);
      } elseif ($subElement->nodeType == XML_TEXT_NODE) {
        $obj["type"] = "paragraph";
        $obj["children"][] = element_to_obj($subElement);
      } else {
        $obj["children"][] = element_to_obj($subElement);
      }
    }
  }

  return $obj;
}

$json = html_to_obj('<h2>Heading</h2><h3>subheadfing</h3><p><strong>Bold </strong>normal text <em>italics</em></p><p><a href="https://www.linkurl.com">link</a></p><ul><li>unordered list 1</li><li>unordered list 2</li></ul><ol><li>list item 1</li><li>list item 2</li></ol>');
echo json_encode($json["children"][0]);

View solution in original post

Reply 1 (1)

tinyman1199
Shopify Partner
9 1 3

This is an accepted solution.

Here is my solution where you pass the html required for your rich text to html_to_obj and then get the first child within the returned object.

function html_to_obj($html)
{
  $dom = new DOMDocument();
  $dom->loadHTML($html);
  return element_to_obj($dom->documentElement);
}

function element_to_obj($element)
{
  $loop = true;
  if ($element->tagName == "body") {
    $obj = array("type" => "root");
  } else {
    if (str_starts_with($element->tagName, "h2")) {
      $obj = array("type" => "heading", "level" => 2);
    } elseif (str_starts_with($element->tagName, "h3")) {
      $obj = array("type" => "heading", "level" => 3);
    } elseif (str_starts_with($element->tagName, "p")) {
      $obj = array("type" => "paragraph");
    } elseif (str_starts_with($element->tagName, "a")) {
      $obj = array("type" => "link");
    } elseif (str_starts_with($element->tagName, "ol")) {
      $obj = array("type" => "list", "listType" => "ordered");
    } elseif (str_starts_with($element->tagName, "ul")) {
      $obj = array("type" => "list", "listType" => "unordered");
    } elseif (str_starts_with($element->tagName, "li")) {
      $obj = array("type" => "list-item");
    } elseif (str_starts_with($element->tagName, "strong")) {
      $obj = array("type" => "text", "bold" => true, "value" => $element->childNodes[0]->wholeText);
      $loop = false;
    } elseif (str_starts_with($element->tagName, "em")) {
      $obj = array("type" => "text", "italic" => true, "value" => $element->childNodes[0]->wholeText);
      $loop = false;
    }
  }
  if ($loop == true) {
    foreach ($element->attributes as $attribute) {
      if ($attribute->name == "href") {
        $obj["url"] = $attribute->value;
      } else {
        $obj[$attribute->name] = $attribute->value;
      }
    }
    foreach ($element->childNodes as $subElement) {
      echo $subElement->nodeType;
      if ($subElement->nodeType == XML_TEXT_NODE) {
        $obj["children"][] =  array("type" => "text", "value" => $subElement->wholeText);
      } elseif ($subElement->nodeType == XML_TEXT_NODE) {
        $obj["type"] = "paragraph";
        $obj["children"][] = element_to_obj($subElement);
      } else {
        $obj["children"][] = element_to_obj($subElement);
      }
    }
  }

  return $obj;
}

$json = html_to_obj('<h2>Heading</h2><h3>subheadfing</h3><p><strong>Bold </strong>normal text <em>italics</em></p><p><a href="https://www.linkurl.com">link</a></p><ul><li>unordered list 1</li><li>unordered list 2</li></ul><ol><li>list item 1</li><li>list item 2</li></ol>');
echo json_encode($json["children"][0]);