Search code examples
jsonscalastring-formatting

Scala create multi-line JSON String


I'm trying to create a multi-line String in Scala as below.

val errorReport: String =
    """
      |{
      |"errorName":"blah",
      |"moreError":"blah2",
      |"errorMessage":{
      |         "status": "bad",
      |         "message": "Unrecognized token 'noformatting': was expecting 'null', 'true', 'false' or NaN
 at [Source: (ByteArrayInputStream); line: 1, column: 25]"
      | }
      |}
      """
   .stripMargin

It's a nested JSON and it's not displaying properly when I print it. The message field inside errorMessage (which is the output of calling getMessage on an instance of a Throwable) is causing the issue because it looks like there is a newline right before

at [Source: ....

If I get rid of that line the JSON displays properly. Any ideas on how to properly format this are appreciated.

EDIT: The issue is with the newline character. So I think the question is more concisely - how to handle the newline within the triple quotes so that it's still recognized as a JSON?

EDIT 2: message is being set by a variable like so:

"message": "${ex.getMessage}"

where ex is a Throwable. An example of the contents of that getMessage call is provided above.


Solution

  • Update 2023: I don't know why anyone would bother generating JSON manually in 2023. Just use some appropriate library that knows how to serialize stuff to JSON.


    (original answer)

    I assume that your question has nothing to do with JSON, and that you're simply asking how to create very wide strings without violating the horizontal 80-character limit in your Scala code. Fortunately, Scala's string literals have at least the following properties:

    • You can go from ordinary code to string-literal mode using quotes "..." and triple quotes """...""".
    • You can go from string-literal mode to ordinary code mode using ${...}
    • Free monoid over characters is reified as methods, that is, there is the + operation that concatenates string literals.
    • The whole construction can be made robust to whitespace and indentation using | and stripMargin.

    All together, it allows you to write down arbitrary string literals without ever violating horizontal character limits, in a way that is robust w.r.t. indentation.

    In this particular case, you want to make a line break in the ambient scala code without introducing a line break in your text. For this, you simply

    • exit the string-literal mode by closing """
    • insert concatenation operator + in code mode
    • make a line-break
    • indent however you want
    • re-enter the string-literal mode again by opening """

    That is,

    """blah-""" +
    """blah"""
    

    will create the string "blah-blah", without line break in the produced string.


    Applied to your concrete problem:

    val errorReport: String = (
        """{
          |  "errorName": "blah",
          |  "moreError": "blah2",
          |  "errorMessage": {
          |    "status": "bad",
          |    "message": "Unrecognized token 'noformatting'""" +
        """: was expecting 'null', 'true', 'false' or NaN at """ +
        """[Source: (ByteArrayInputStream); line: 1, column: 25]"
          |  }
          |}
          """
      ).stripMargin
    

    Maybe a more readable option would be to construct the lengthy message separately from the neatly indented JSON, and then use string interpolation to combine the two components:

    val errorReport: String = {
      val msg = 
        """Unrecognized token 'noformatting': """ +
        """was expecting 'null', 'true', 'false' or NaN at """ +
        """[Source: (ByteArrayInputStream); line: 1, column: 25]"""
    
        s"""{
          |  "errorName": "blah",
          |  "moreError": "blah2",
          |  "errorMessage": {
          |    "status": "bad",
          |    "message": "${msg}"
          |  }
          |}
          """
      }.stripMargin
    

    If the message itself contains line breaks

    Since JSON does not allow multiline string literals, you have to do something else:

    • To remove line breaks, use .replaceAll("\\n", "") or rather .replaceAll("\\n", " ")
    • To encode line breaks with the escape sequence \n, use .replaceAll("\\n", "\\\\n") (yes... backslashes...)