@@ -41,6 +41,11 @@ import qualified Data.Aeson.Types as A
4141-- The Show instance for PSString produces a string literal which would
4242-- represent the same data were it inserted into a PureScript source file.
4343--
44+-- Because JSON parsers vary wildly in terms of how they deal with lone
45+-- surrogates in JSON strings, the ToJSON instance for PSString produces JSON
46+-- strings where that would be safe (i.e. when there are no lone surrogates),
47+-- and arrays of UTF-16 code units (integers) otherwise.
48+--
4449newtype PSString = PSString { toUTF16CodeUnits :: [Word16] }
4550 deriving (Eq, Ord, Monoid)
4651
@@ -117,12 +122,17 @@ instance IsString PSString where
117122 encodeUTF16 c = [toWord $ fromEnum c]
118123
119124instance A.ToJSON PSString where
120− toJSON = A.toJSON . toUTF16CodeUnits
125+ toJSON str =
126+ case decodeString str of
127+ Just t -> A.toJSON t
128+ Nothing -> A.toJSON (toUTF16CodeUnits str)
121129
122130instance A.FromJSON PSString where
123− parseJSON a = currentParser <|> backwardsCompat
131+ parseJSON a = jsonString <|> arrayOfCodeUnits
124132 where
125− currentParser = PSString <$> parseArrayOfCodeUnits a
133+ jsonString = fromString <$> A.parseJSON a
134+
135+ arrayOfCodeUnits = PSString <$> parseArrayOfCodeUnits a
126136
127137 parseArrayOfCodeUnits :: A.Value -> A.Parser [Word16]
128138 parseArrayOfCodeUnits = A.withArray "array of UTF-16 code units" (traverse parseCodeUnit . V.toList)
@@ -130,10 +140,6 @@ instance A.FromJSON PSString where
130140 parseCodeUnit :: A.Value -> A.Parser Word16
131141 parseCodeUnit b = A.withScientific "two-byte non-negative integer" (maybe (A.typeMismatch "" b) return . toBoundedInteger) b
132142
133− -- For backwards compatibility: this allows us to parse JSON produced by
134− -- 0.10.4 or earlier
135− backwardsCompat = fromString <$> A.parseJSON a
136−
137143-- |
138144-- Pretty print a PSString, using JavaScript escape sequences. Intended for
139145-- use in compiled JS output.