// Copyright (c) All contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Buffers; using System.Globalization; using System.IO; using System.Text; using System.Threading; using MessagePack.Formatters; using Nerdbank.Streams; namespace MessagePack { // JSON API public partial class MessagePackSerializer { /// /// Serialize an object to JSON string. /// /// Thrown if an error occurs during serialization. public static void SerializeToJson(TextWriter textWriter, T obj, MessagePackSerializerOptions options = null, CancellationToken cancellationToken = default) { options = options ?? DefaultOptions; using (var sequenceRental = options.SequencePool.Rent()) { var msgpackWriter = new MessagePackWriter(sequenceRental.Value) { CancellationToken = cancellationToken, }; Serialize(ref msgpackWriter, obj, options); msgpackWriter.Flush(); var msgpackReader = new MessagePackReader(sequenceRental.Value) { CancellationToken = cancellationToken, }; ConvertToJson(ref msgpackReader, textWriter, options); } } /// /// Serialize an object to JSON string. /// /// Thrown if an error occurs during serialization. public static string SerializeToJson(T obj, MessagePackSerializerOptions options = null, CancellationToken cancellationToken = default) { var writer = new StringWriter(); SerializeToJson(writer, obj, options, cancellationToken); return writer.ToString(); } /// /// Convert a message-pack binary to a JSON string. /// /// Thrown if an error occurs while reading the messagepack data or writing out the JSON. public static string ConvertToJson(ReadOnlyMemory bytes, MessagePackSerializerOptions options = null, CancellationToken cancellationToken = default) => ConvertToJson(new ReadOnlySequence(bytes), options, cancellationToken); /// /// Convert a message-pack binary to a JSON string. /// /// Thrown if an error occurs while reading the messagepack data or writing out the JSON. public static string ConvertToJson(in ReadOnlySequence bytes, MessagePackSerializerOptions options = null, CancellationToken cancellationToken = default) { var jsonWriter = new StringWriter(); var reader = new MessagePackReader(bytes) { CancellationToken = cancellationToken, }; ConvertToJson(ref reader, jsonWriter, options); return jsonWriter.ToString(); } /// /// Convert a message-pack binary to a JSON string. /// /// Thrown if an error occurs while reading the messagepack data or writing out the JSON. public static void ConvertToJson(ref MessagePackReader reader, TextWriter jsonWriter, MessagePackSerializerOptions options = null) { if (reader.End) { return; } options = options ?? DefaultOptions; try { if (options.Compression.IsCompression()) { using (var scratchRental = options.SequencePool.Rent()) { if (TryDecompress(ref reader, scratchRental.Value)) { var scratchReader = new MessagePackReader(scratchRental.Value) { CancellationToken = reader.CancellationToken, }; if (scratchReader.End) { return; } ToJsonCore(ref scratchReader, jsonWriter, options); } else { ToJsonCore(ref reader, jsonWriter, options); } } } else { ToJsonCore(ref reader, jsonWriter, options); } } catch (Exception ex) { throw new MessagePackSerializationException("Error occurred while translating msgpack to JSON.", ex); } } /// /// Translates the given JSON to MessagePack. /// public static void ConvertFromJson(string str, ref MessagePackWriter writer, MessagePackSerializerOptions options = null) { using (var sr = new StringReader(str)) { ConvertFromJson(sr, ref writer, options); } } /// /// Translates the given JSON to MessagePack. /// public static byte[] ConvertFromJson(string str, MessagePackSerializerOptions options = null, CancellationToken cancellationToken = default) { options = options ?? DefaultOptions; using (var scratchRental = options.SequencePool.Rent()) { var writer = new MessagePackWriter(scratchRental.Value) { CancellationToken = cancellationToken, }; using (var sr = new StringReader(str)) { ConvertFromJson(sr, ref writer, options); } writer.Flush(); return scratchRental.Value.AsReadOnlySequence.ToArray(); } } /// /// Translates the given JSON to MessagePack. /// public static void ConvertFromJson(TextReader reader, ref MessagePackWriter writer, MessagePackSerializerOptions options = null) { options = options ?? DefaultOptions; if (options.Compression.IsCompression()) { using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); using (var jr = new TinyJsonReader(reader, false)) { FromJsonCore(jr, ref scratchWriter, options); } scratchWriter.Flush(); ToLZ4BinaryCore(scratchRental.Value, ref writer, options.Compression); } } else { using (var jr = new TinyJsonReader(reader, false)) { FromJsonCore(jr, ref writer, options); } } } private static uint FromJsonCore(TinyJsonReader jr, ref MessagePackWriter writer, MessagePackSerializerOptions options) { uint count = 0; while (jr.Read()) { switch (jr.TokenType) { case TinyJsonToken.None: break; case TinyJsonToken.StartObject: // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); var mapCount = FromJsonCore(jr, ref scratchWriter, options); scratchWriter.Flush(); mapCount = mapCount / 2; // remove propertyname string count. writer.WriteMapHeader(mapCount); writer.WriteRaw(scratchRental.Value); } count++; break; case TinyJsonToken.EndObject: return count; // break case TinyJsonToken.StartArray: // Set up a scratch area to serialize the collection since we don't know its length yet, which must be written first. using (var scratchRental = options.SequencePool.Rent()) { MessagePackWriter scratchWriter = writer.Clone(scratchRental.Value); var arrayCount = FromJsonCore(jr, ref scratchWriter, options); scratchWriter.Flush(); writer.WriteArrayHeader(arrayCount); writer.WriteRaw(scratchRental.Value); } count++; break; case TinyJsonToken.EndArray: return count; // break case TinyJsonToken.Number: ValueType v = jr.ValueType; if (v == ValueType.Double) { writer.Write(jr.DoubleValue); } else if (v == ValueType.Long) { writer.Write(jr.LongValue); } else if (v == ValueType.ULong) { writer.Write(jr.ULongValue); } else if (v == ValueType.Decimal) { DecimalFormatter.Instance.Serialize(ref writer, jr.DecimalValue, null); } count++; break; case TinyJsonToken.String: writer.Write(jr.StringValue); count++; break; case TinyJsonToken.True: writer.Write(true); count++; break; case TinyJsonToken.False: writer.Write(false); count++; break; case TinyJsonToken.Null: writer.WriteNil(); count++; break; default: break; } } return count; } private static void ToJsonCore(ref MessagePackReader reader, TextWriter writer, MessagePackSerializerOptions options) { MessagePackType type = reader.NextMessagePackType; switch (type) { case MessagePackType.Integer: if (MessagePackCode.IsSignedInteger(reader.NextCode)) { writer.Write(reader.ReadInt64().ToString(CultureInfo.InvariantCulture)); } else { writer.Write(reader.ReadUInt64().ToString(CultureInfo.InvariantCulture)); } break; case MessagePackType.Boolean: writer.Write(reader.ReadBoolean() ? "true" : "false"); break; case MessagePackType.Float: if (reader.NextCode == MessagePackCode.Float32) { writer.Write(reader.ReadSingle().ToString(CultureInfo.InvariantCulture)); } else { writer.Write(reader.ReadDouble().ToString(CultureInfo.InvariantCulture)); } break; case MessagePackType.String: WriteJsonString(reader.ReadString(), writer); break; case MessagePackType.Binary: ArraySegment segment = ByteArraySegmentFormatter.Instance.Deserialize(ref reader, options); writer.Write("\"" + Convert.ToBase64String(segment.Array, segment.Offset, segment.Count) + "\""); break; case MessagePackType.Array: { int length = reader.ReadArrayHeader(); options.Security.DepthStep(ref reader); try { writer.Write("["); for (int i = 0; i < length; i++) { ToJsonCore(ref reader, writer, options); if (i != length - 1) { writer.Write(","); } } writer.Write("]"); } finally { reader.Depth--; } return; } case MessagePackType.Map: { int length = reader.ReadMapHeader(); options.Security.DepthStep(ref reader); try { writer.Write("{"); for (int i = 0; i < length; i++) { // write key { MessagePackType keyType = reader.NextMessagePackType; if (keyType == MessagePackType.String || keyType == MessagePackType.Binary) { ToJsonCore(ref reader, writer, options); } else { writer.Write("\""); ToJsonCore(ref reader, writer, options); writer.Write("\""); } } writer.Write(":"); // write body { ToJsonCore(ref reader, writer, options); } if (i != length - 1) { writer.Write(","); } } writer.Write("}"); } finally { reader.Depth--; } return; } case MessagePackType.Extension: ExtensionHeader extHeader = reader.ReadExtensionFormatHeader(); if (extHeader.TypeCode == ReservedMessagePackExtensionTypeCode.DateTime) { DateTime dt = reader.ReadDateTime(extHeader); writer.Write("\""); writer.Write(dt.ToString("o", CultureInfo.InvariantCulture)); writer.Write("\""); } #if !UNITY_2018_3_OR_NEWER else if (extHeader.TypeCode == ThisLibraryExtensionTypeCodes.TypelessFormatter) { // prepare type name token var privateBuilder = new StringBuilder(); var typeNameTokenBuilder = new StringBuilder(); SequencePosition positionBeforeTypeNameRead = reader.Position; ToJsonCore(ref reader, new StringWriter(typeNameTokenBuilder), options); int typeNameReadSize = (int)reader.Sequence.Slice(positionBeforeTypeNameRead, reader.Position).Length; if (extHeader.Length > typeNameReadSize) { // object map or array MessagePackType typeInside = reader.NextMessagePackType; if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) { privateBuilder.Append("{"); } ToJsonCore(ref reader, new StringWriter(privateBuilder), options); // insert type name token to start of object map or array if (typeInside != MessagePackType.Array) { typeNameTokenBuilder.Insert(0, "\"$type\":"); } if (typeInside != MessagePackType.Array && typeInside != MessagePackType.Map) { privateBuilder.Append("}"); } if (privateBuilder.Length > 2) { typeNameTokenBuilder.Append(","); } privateBuilder.Insert(1, typeNameTokenBuilder.ToString()); writer.Write(privateBuilder.ToString()); } else { writer.Write("{\"$type\":\"" + typeNameTokenBuilder.ToString() + "}"); } } #endif else { var data = reader.ReadRaw((long)extHeader.Length); writer.Write("["); writer.Write(extHeader.TypeCode); writer.Write(","); writer.Write("\""); writer.Write(Convert.ToBase64String(data.ToArray())); writer.Write("\""); writer.Write("]"); } break; case MessagePackType.Nil: reader.Skip(); writer.Write("null"); break; default: throw new MessagePackSerializationException($"code is invalid. code: {reader.NextCode} format: {MessagePackCode.ToFormatName(reader.NextCode)}"); } } // escape string private static void WriteJsonString(string value, TextWriter builder) { builder.Write('\"'); var len = value.Length; for (int i = 0; i < len; i++) { var c = value[i]; switch (c) { case '"': builder.Write("\\\""); break; case '\\': builder.Write("\\\\"); break; case '\b': builder.Write("\\b"); break; case '\f': builder.Write("\\f"); break; case '\n': builder.Write("\\n"); break; case '\r': builder.Write("\\r"); break; case '\t': builder.Write("\\t"); break; default: builder.Write(c); break; } } builder.Write('\"'); } } }