Solved

Rich Text metafield JSON input via GraphQL

tinyman1199
Shopify Partner
9 1 1

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 1

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 1

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]);