Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Core/JsonWriter.cs
2025-05-18 13:04:45 +08:00

435 lines
9.5 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace EpicGames.Core
{
/// <summary>
/// Specifies how to format JSON output
/// </summary>
public enum JsonWriterStyle
{
/// <summary>
/// Omit spaces between elements
/// </summary>
Compact,
/// <summary>
/// Put each value on a newline, and indent output
/// </summary>
Readable
}
/// <summary>
/// Writer for JSON data, which indents the output text appropriately, and adds commas and newlines between fields
/// </summary>
public sealed class JsonWriter : IDisposable
{
TextWriter _writer;
readonly bool _leaveOpen;
readonly JsonWriterStyle _style;
bool _bRequiresComma;
string _indent;
/// <summary>
/// Constructor
/// </summary>
/// <param name="fileName">File to write to</param>
/// <param name="style">Should use packed JSON or not</param>
public JsonWriter(string fileName, JsonWriterStyle style = JsonWriterStyle.Readable)
: this(new StreamWriter(fileName))
{
_style = style;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="fileName">File to write to</param>
/// <param name="style">Should use packed JSON or not</param>
public JsonWriter(FileReference fileName, JsonWriterStyle style = JsonWriterStyle.Readable)
: this(new StreamWriter(fileName.FullName))
{
_style = style;
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="writer">The text writer to output to</param>
/// <param name="leaveOpen">Whether to leave the writer open when the object is disposed</param>
/// <param name="style">The output style</param>
public JsonWriter(TextWriter writer, bool leaveOpen = false, JsonWriterStyle style = JsonWriterStyle.Readable)
{
_writer = writer;
_leaveOpen = leaveOpen;
_style = style;
_indent = "";
}
/// <summary>
/// Dispose of any managed resources
/// </summary>
public void Dispose()
{
if(!_leaveOpen && _writer != null)
{
_writer.Dispose();
_writer = null!;
}
}
private void IncreaseIndent()
{
if (_style == JsonWriterStyle.Readable)
{
_indent += "\t";
}
}
private void DecreaseIndent()
{
if (_style == JsonWriterStyle.Readable)
{
_indent = _indent.Substring(0, _indent.Length - 1);
}
}
/// <summary>
/// Write the opening brace for an object
/// </summary>
public void WriteObjectStart()
{
WriteCommaNewline();
_writer.Write(_indent);
_writer.Write("{");
IncreaseIndent();
_bRequiresComma = false;
}
/// <summary>
/// Write the name and opening brace for an object
/// </summary>
/// <param name="objectName">Name of the field</param>
public void WriteObjectStart(string objectName)
{
WriteCommaNewline();
WriteName(objectName);
_bRequiresComma = false;
WriteObjectStart();
}
/// <summary>
/// Write the closing brace for an object
/// </summary>
public void WriteObjectEnd()
{
DecreaseIndent();
WriteLine();
_writer.Write(_indent);
_writer.Write("}");
_bRequiresComma = true;
}
/// <summary>
/// Write the opening bracket for an unnamed array
/// </summary>
public void WriteArrayStart()
{
WriteCommaNewline();
_writer.Write("{0}[", _indent);
IncreaseIndent();
_bRequiresComma = false;
}
/// <summary>
/// Write the name and opening bracket for an array
/// </summary>
/// <param name="arrayName">Name of the field</param>
public void WriteArrayStart(string arrayName)
{
WriteCommaNewline();
WriteName(arrayName);
_writer.Write('[');
IncreaseIndent();
_bRequiresComma = false;
}
/// <summary>
/// Write the closing bracket for an array
/// </summary>
public void WriteArrayEnd()
{
DecreaseIndent();
WriteLine();
_writer.Write("{0}]", _indent);
_bRequiresComma = true;
}
private void WriteLine()
{
if (_style == JsonWriterStyle.Readable)
{
_writer.WriteLine();
}
}
private void WriteLine(string line)
{
if (_style == JsonWriterStyle.Readable)
{
_writer.WriteLine(line);
}
else
{
_writer.Write(line);
}
}
/// <summary>
/// Write an array of strings
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="values">Values for the field</param>
public void WriteStringArrayField(string name, IEnumerable<string> values)
{
WriteArrayStart(name);
foreach(string value in values)
{
WriteValue(value);
}
WriteArrayEnd();
}
/// <summary>
/// Write an array of enum values
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="values">Values for the field</param>
public void WriteEnumArrayField<T>(string name, IEnumerable<T> values) where T : struct
{
WriteStringArrayField(name, values.Select(x => x.ToString()!));
}
/// <summary>
/// Write a value with no field name, for the contents of an array
/// </summary>
/// <param name="value">Value to write</param>
public void WriteValue(int value)
{
WriteCommaNewline();
_writer.Write(_indent);
_writer.Write(value);
_bRequiresComma = true;
}
/// <summary>
/// Write a value with no field name, for the contents of an array
/// </summary>
/// <param name="value">Value to write</param>
public void WriteValue(string value)
{
WriteCommaNewline();
_writer.Write(_indent);
WriteEscapedString(value);
_bRequiresComma = true;
}
/// <summary>
/// Write a field name and string value
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteValue(string name, string? value)
{
WriteCommaNewline();
WriteName(name);
WriteEscapedString(value);
_bRequiresComma = true;
}
/// <summary>
/// Write a field name and integer value
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteValue(string name, int value)
{
WriteValueInternal(name, value.ToString());
}
/// <summary>
/// Write a field name and unsigned integer value
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteValue(string name, uint value)
{
WriteValueInternal(name, value.ToString());
}
/// <summary>
/// Write a field name and double value
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteValue(string name, double value)
{
WriteValueInternal(name, value.ToString());
}
/// <summary>
/// Write a field name and bool value
/// </summary>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteValue(string name, bool value)
{
WriteValueInternal(name, value ? "true" : "false");
}
/// <summary>
/// Write a field name and enum value
/// </summary>
/// <typeparam name="T">The enum type</typeparam>
/// <param name="name">Name of the field</param>
/// <param name="value">Value for the field</param>
public void WriteEnumValue<T>(string name, T value) where T : struct
{
WriteValue(name, value.ToString()!);
}
void WriteCommaNewline()
{
if (_bRequiresComma)
{
WriteLine(",");
}
else if (_indent.Length > 0)
{
WriteLine();
}
}
void WriteName(string name)
{
string space = (_style == JsonWriterStyle.Readable) ? " " : "";
_writer.Write(_indent);
WriteEscapedString(name);
_writer.Write(":{0}", space);
}
void WriteValueInternal(string name, string value)
{
WriteCommaNewline();
WriteName(name);
_writer.Write(value);
_bRequiresComma = true;
}
void WriteEscapedString(string? value)
{
// Escape any characters which may not appear in a JSON string (see http://www.json.org).
_writer.Write("\"");
if (value != null)
{
_writer.Write(EscapeString(value));
}
_writer.Write("\"");
}
/// <summary>
/// Escapes a string for serializing to JSON
/// </summary>
/// <param name="value">The string to escape</param>
/// <returns>The escaped string</returns>
public static string EscapeString(string value)
{
// Prescan the string looking for things to escape. If not found, we don't need to
// create the string builder
int idx = 0;
for (; idx < value.Length; idx++)
{
char c = value[idx];
if (c == '\"' || c == '\\' || Char.IsControl(c))
{
break;
}
}
if (idx == value.Length)
{
return value;
}
// Otherwise, create the string builder, append the known portion that doesn't have an escape
// and continue processing the string starting at the first character needing to be escaped.
StringBuilder result = new StringBuilder();
result.Append(value.AsSpan(0, idx));
for (; idx < value.Length; idx++)
{
switch (value[idx])
{
case '\"':
result.Append("\\\"");
break;
case '\\':
result.Append("\\\\");
break;
case '\b':
result.Append("\\b");
break;
case '\f':
result.Append("\\f");
break;
case '\n':
result.Append("\\n");
break;
case '\r':
result.Append("\\r");
break;
case '\t':
result.Append("\\t");
break;
default:
if (Char.IsControl(value[idx]))
{
result.AppendFormat("\\u{0:X4}", (int)value[idx]);
}
else
{
result.Append(value[idx]);
}
break;
}
}
return result.ToString();
}
}
}