At first glance, it looked complicated and it just felt like something that should have come out-of-the-box.
Well, here is the Extension Method:
    public static class ExtensionMethods {
        public static string OuterText(this JToken token) {
           
var sw = new StringWriter();
           
token.OuterText(sw);
           
return sw.ToString();
        }
        public static void OuterText(this JToken token, StringWriter sw) {
            switch (token.Type) {
               
case JTokenType.String:
                   
sw.WriteLine(token.ToString());
                   
break;
               
case JTokenType.Property:
               
case JTokenType.Array:
               
case JTokenType.Object:
                   
foreach (var item in token.Values()) {
                       
OuterText(item, sw);
                   
}
                   
break;
 
          }
        }
    }
Here's an easy way of using it:
       public void Test() {
            var jsonString = "{ \"Title\":\"Extending Newtonsoft
JSON JToken to output only the text values\", \"OtherStuff\":[ {\"text\": \"I sure wish I had a way of converting a JSON Object to text, but
without it being in JSON format...\"}, \"Wait a minute, ... I CAN!\", { \"objectContainingText\": { \"numberProp\": 1, \"stringProp\": \"Hooray for extension methods!\" } } ] }";
 
            var jsonToken = JsonConvert.DeserializeObject<JToken>(jsonString);
           
var jsonText = jsonToken.OuterText();
       }
Here's the result:
Extending Newtonsoft JSON JToken to output only the text values
I sure wish I had a way of converting a JSON Object to text, but without it being in JSON format...
Wait a minute, ... I CAN!
Hooray for extension methods!
Originally, I was deserializing to a JObject, however JObject is also a JToken, so I wrote the extension method to be versatile enough to handle any of those other types.