Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #776: Add settings for the kind of newline to use #2231

Merged
merged 21 commits into from
Feb 12, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions gson/src/main/java/com/google/gson/Gson.java
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
*
* <p>You can create a Gson instance by invoking {@code new Gson()} if the default configuration
* is all you need. You can also use {@link GsonBuilder} to build a Gson instance with various
* configuration options such as versioning support, pretty printing, custom
* configuration options such as versioning support, pretty printing, custom newline, custom
* {@link JsonSerializer}s, {@link JsonDeserializer}s, and {@link InstanceCreator}s.</p>
*
* <p>Here is an example of how Gson is used for a simple Class:
Expand Down Expand Up @@ -142,6 +142,7 @@ public final class Gson {
static final boolean DEFAULT_JSON_NON_EXECUTABLE = false;
static final boolean DEFAULT_LENIENT = false;
static final boolean DEFAULT_PRETTY_PRINT = false;
static final NewlineStyle DEFAULT_NEWLINE_STYLE = NewlineStyle.MACOS_AND_LINUX;
static final boolean DEFAULT_ESCAPE_HTML = true;
static final boolean DEFAULT_SERIALIZE_NULLS = false;
static final boolean DEFAULT_COMPLEX_MAP_KEYS = false;
Expand Down Expand Up @@ -179,6 +180,7 @@ public final class Gson {
final boolean generateNonExecutableJson;
final boolean htmlSafe;
final boolean prettyPrinting;
final NewlineStyle newlineStyle;
final boolean lenient;
final boolean serializeSpecialFloatingPointValues;
final boolean useJdkUnsafe;
Expand All @@ -199,6 +201,8 @@ public final class Gson {
* <li>The JSON generated by <code>toJson</code> methods is in compact representation. This
* means that all the unneeded white-space is removed. You can change this behavior with
* {@link GsonBuilder#setPrettyPrinting()}. </li>
* <li>When the JSON generated contains more than one line, the kind of newline to use
* can be configured with {@link GsonBuilder#setNewlineStyle(NewlineStyle)}.</li>
* <li>The generated JSON omits all the fields that are null. Note that nulls in arrays are
* kept as is since an array is an ordered list. Moreover, if a field is not null, but its
* generated JSON is empty, the field is kept. You can configure Gson to serialize null values
Expand Down Expand Up @@ -230,8 +234,8 @@ public Gson() {
this(Excluder.DEFAULT, DEFAULT_FIELD_NAMING_STRATEGY,
Collections.<Type, InstanceCreator<?>>emptyMap(), DEFAULT_SERIALIZE_NULLS,
DEFAULT_COMPLEX_MAP_KEYS, DEFAULT_JSON_NON_EXECUTABLE, DEFAULT_ESCAPE_HTML,
DEFAULT_PRETTY_PRINT, DEFAULT_LENIENT, DEFAULT_SPECIALIZE_FLOAT_VALUES,
DEFAULT_USE_JDK_UNSAFE,
DEFAULT_PRETTY_PRINT, DEFAULT_NEWLINE_STYLE, DEFAULT_LENIENT,
DEFAULT_SPECIALIZE_FLOAT_VALUES, DEFAULT_USE_JDK_UNSAFE,
LongSerializationPolicy.DEFAULT, DEFAULT_DATE_PATTERN, DateFormat.DEFAULT, DateFormat.DEFAULT,
Collections.<TypeAdapterFactory>emptyList(), Collections.<TypeAdapterFactory>emptyList(),
Collections.<TypeAdapterFactory>emptyList(), DEFAULT_OBJECT_TO_NUMBER_STRATEGY, DEFAULT_NUMBER_TO_NUMBER_STRATEGY,
Expand All @@ -241,8 +245,8 @@ public Gson() {
Gson(Excluder excluder, FieldNamingStrategy fieldNamingStrategy,
Map<Type, InstanceCreator<?>> instanceCreators, boolean serializeNulls,
boolean complexMapKeySerialization, boolean generateNonExecutableGson, boolean htmlSafe,
boolean prettyPrinting, boolean lenient, boolean serializeSpecialFloatingPointValues,
boolean useJdkUnsafe,
boolean prettyPrinting, NewlineStyle newlineStyle, boolean lenient,
boolean serializeSpecialFloatingPointValues, boolean useJdkUnsafe,
LongSerializationPolicy longSerializationPolicy, String datePattern, int dateStyle,
int timeStyle, List<TypeAdapterFactory> builderFactories,
List<TypeAdapterFactory> builderHierarchyFactories,
Expand All @@ -258,6 +262,7 @@ public Gson() {
this.generateNonExecutableJson = generateNonExecutableGson;
this.htmlSafe = htmlSafe;
this.prettyPrinting = prettyPrinting;
this.newlineStyle = newlineStyle;
this.lenient = lenient;
this.serializeSpecialFloatingPointValues = serializeSpecialFloatingPointValues;
this.useJdkUnsafe = useJdkUnsafe;
Expand Down Expand Up @@ -876,6 +881,7 @@ public void toJson(JsonElement jsonElement, Appendable writer) throws JsonIOExce
* <li>{@link GsonBuilder#serializeNulls()}</li>
* <li>{@link GsonBuilder#setLenient()}</li>
* <li>{@link GsonBuilder#setPrettyPrinting()}</li>
* <li>{@link GsonBuilder#setNewlineStyle(NewlineStyle)}</li>
* </ul>
*/
public JsonWriter newJsonWriter(Writer writer) throws IOException {
Expand All @@ -886,6 +892,21 @@ public JsonWriter newJsonWriter(Writer writer) throws IOException {
if (prettyPrinting) {
jsonWriter.setIndent(" ");
}
switch (newlineStyle) {
case CURRENT_OS:
jsonWriter.setNewline(System.lineSeparator());
break;
case WINDOWS:
jsonWriter.setNewline("\r\n");
break;
case OLD_MACOS:
jsonWriter.setNewline("\r");
break;
case MACOS_AND_LINUX: // intentional fallthrough
default:
mihnita marked this conversation as resolved.
Show resolved Hide resolved
mihnita marked this conversation as resolved.
Show resolved Hide resolved
jsonWriter.setNewline("\n");
break;
}
jsonWriter.setHtmlSafe(htmlSafe);
jsonWriter.setLenient(lenient);
jsonWriter.setSerializeNulls(serializeNulls);
Expand Down
17 changes: 16 additions & 1 deletion gson/src/main/java/com/google/gson/GsonBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static com.google.gson.Gson.DEFAULT_NUMBER_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_OBJECT_TO_NUMBER_STRATEGY;
import static com.google.gson.Gson.DEFAULT_PRETTY_PRINT;
import static com.google.gson.Gson.DEFAULT_NEWLINE_STYLE;
import static com.google.gson.Gson.DEFAULT_SERIALIZE_NULLS;
import static com.google.gson.Gson.DEFAULT_SPECIALIZE_FLOAT_VALUES;
import static com.google.gson.Gson.DEFAULT_USE_JDK_UNSAFE;
Expand Down Expand Up @@ -67,6 +68,7 @@
* .setDateFormat(DateFormat.LONG)
* .setFieldNamingPolicy(FieldNamingPolicy.UPPER_CAMEL_CASE)
* .setPrettyPrinting()
* .setNewlineStyle(NewlineStyle.CURRENT_OS)
* .setVersion(1.0)
* .create();
* </pre>
Expand Down Expand Up @@ -99,6 +101,7 @@ public final class GsonBuilder {
private boolean serializeSpecialFloatingPointValues = DEFAULT_SPECIALIZE_FLOAT_VALUES;
private boolean escapeHtmlChars = DEFAULT_ESCAPE_HTML;
private boolean prettyPrinting = DEFAULT_PRETTY_PRINT;
private NewlineStyle newlineStyle = DEFAULT_NEWLINE_STYLE;
private boolean generateNonExecutableJson = DEFAULT_JSON_NON_EXECUTABLE;
private boolean lenient = DEFAULT_LENIENT;
private boolean useJdkUnsafe = DEFAULT_USE_JDK_UNSAFE;
Expand Down Expand Up @@ -130,6 +133,7 @@ public GsonBuilder() {
this.generateNonExecutableJson = gson.generateNonExecutableJson;
this.escapeHtmlChars = gson.htmlSafe;
this.prettyPrinting = gson.prettyPrinting;
this.newlineStyle = gson.newlineStyle;
this.lenient = gson.lenient;
this.serializeSpecialFloatingPointValues = gson.serializeSpecialFloatingPointValues;
this.longSerializationPolicy = gson.longSerializationPolicy;
Expand Down Expand Up @@ -488,6 +492,17 @@ public GsonBuilder setPrettyPrinting() {
return this;
}

/**
* Configures Gson to output JSON that uses a certain kind of newline.
* This option only affects JSON serialization.
mihnita marked this conversation as resolved.
Show resolved Hide resolved
*
* @return a reference to this {@code GsonBuilder} object to fulfill the "Builder" pattern
*/
mihnita marked this conversation as resolved.
Show resolved Hide resolved
public GsonBuilder setNewlineStyle(NewlineStyle newlineStyle) {
this.newlineStyle = Objects.requireNonNull(newlineStyle);
return this;
}

/**
* Configures Gson to allow JSON data which does not strictly comply with the JSON specification.
*
Expand Down Expand Up @@ -761,7 +776,7 @@ public Gson create() {

return new Gson(excluder, fieldNamingPolicy, new HashMap<>(instanceCreators),
serializeNulls, complexMapKeySerialization,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, lenient,
generateNonExecutableJson, escapeHtmlChars, prettyPrinting, newlineStyle, lenient,
serializeSpecialFloatingPointValues, useJdkUnsafe, longSerializationPolicy,
datePattern, dateStyle, timeStyle, new ArrayList<>(this.factories),
new ArrayList<>(this.hierarchyFactories), factories,
Expand Down
54 changes: 54 additions & 0 deletions gson/src/main/java/com/google/gson/NewlineStyle.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2022 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.gson;

/**
* An enumeration that defines the kind of newline to use for serialization.
*
* @see <a href="https://en.wikipedia.org/wiki/Newline">Wikipedia Newline article</a>
*
* @since $next-version$
*/
public enum NewlineStyle {
/**
* Using this style will result in the same kind of newline that the current environment uses.
*
* <p>So it will produce {@code "\r\n"} when running on Windows, and {@code "\n"} when running
* on macOS &amp; Linux.</p>
mihnita marked this conversation as resolved.
Show resolved Hide resolved
mihnita marked this conversation as resolved.
Show resolved Hide resolved
*
* @see System#lineSeparator()
*/
CURRENT_OS,

/**
* Using this style will result in the same newline convention that Windows uses
* (and MS-DOS used). This is {@code CR+LF} ({@code 0D 0A}, {@code "\r\n"}).
*/
WINDOWS,

/**
* Using this style will result in the same newline convention that macOS, Linux, and UNIX-like
* systems use. This is {@code LF} ({@code 0A}, {@code "\n"}).
*/
MACOS_AND_LINUX,

/**
* Using this style will result in the same newline convention that classic Mac OS used.
* Rarely needed. This is {@code CR} ({@code 0D}, {@code "\r"}).
*/
OLD_MACOS;
}
23 changes: 22 additions & 1 deletion gson/src/main/java/com/google/gson/stream/JsonWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ public class JsonWriter implements Closeable, Flushable {
*/
private String indent;

/**
* A string to be used as newline.
*/
private String newline = "\n";

/**
* The name/value separator; either ":" or ": ".
*/
Expand Down Expand Up @@ -225,6 +230,22 @@ public final void setIndent(String indent) {
}
}

/**
* Sets the newline string to be used in the encoded document.
mihnita marked this conversation as resolved.
Show resolved Hide resolved
*
* @param newline the string that will be used for newline.
*/
public final void setNewline(String newline) {
mihnita marked this conversation as resolved.
Show resolved Hide resolved
this.newline = Objects.requireNonNull(newline, "newline == null");
}

/**
* Returns the newline string used by this writer.
*/
public final String getNewline() {
mihnita marked this conversation as resolved.
Show resolved Hide resolved
mihnita marked this conversation as resolved.
Show resolved Hide resolved
return newline;
}

/**
* Configure this writer to relax its syntax rules. By default, this writer
* only emits well-formed JSON as specified by <a
Expand Down Expand Up @@ -649,7 +670,7 @@ private void newline() throws IOException {
return;
}

out.write('\n');
out.write(newline);
for (int i = 1, size = stackSize; i < size; i++) {
out.write(indent);
}
Expand Down
6 changes: 3 additions & 3 deletions gson/src/test/java/com/google/gson/GsonTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 The Gson Authors
* Copyright (C) 2022 The Gson Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -57,7 +57,7 @@ public final class GsonTest extends TestCase {
public void testOverridesDefaultExcluder() {
Gson gson = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
true, NewlineStyle.MACOS_AND_LINUX, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
Expand All @@ -72,7 +72,7 @@ public void testOverridesDefaultExcluder() {
public void testClonedTypeAdapterFactoryListsAreIndependent() {
Gson original = new Gson(CUSTOM_EXCLUDER, CUSTOM_FIELD_NAMING_STRATEGY,
new HashMap<Type, InstanceCreator<?>>(), true, false, true, false,
true, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
true, NewlineStyle.MACOS_AND_LINUX, true, false, true, LongSerializationPolicy.DEFAULT, null, DateFormat.DEFAULT,
DateFormat.DEFAULT, new ArrayList<TypeAdapterFactory>(),
new ArrayList<TypeAdapterFactory>(), new ArrayList<TypeAdapterFactory>(),
CUSTOM_OBJECT_TO_NUMBER_STRATEGY, CUSTOM_NUMBER_TO_NUMBER_STRATEGY,
Expand Down
97 changes: 97 additions & 0 deletions gson/src/test/java/com/google/gson/functional/NewlineTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2022 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.gson.functional;

import static org.junit.Assert.assertEquals;

import java.util.LinkedHashMap;
import java.util.Map;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.NewlineStyle;

/**
* Functional tests for newline option.
*
* @author Mihai Nita
*/
@RunWith(JUnit4.class)
public class NewlineTest {

private Map<String, Integer> map;
private static final String EXPECTED = "{<EOL> \"abc\": 1,<EOL> \"def\": 5<EOL>}";

@Before
public void setUp() {
map = new LinkedHashMap<>();
map.put("abc", 1);
map.put("def", 5);
}

@Test
public void testNewlineDefault() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
String json = gson.toJson(map);
assertEquals(EXPECTED.replace("<EOL>", "\n"), json);
}

@Test
public void testNewlineCrLf() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setNewlineStyle(NewlineStyle.WINDOWS)
.create();
String json = gson.toJson(map);
assertEquals(EXPECTED.replace("<EOL>", "\r\n"), json);
}

@Test
public void testNewlineLf() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setNewlineStyle(NewlineStyle.MACOS_AND_LINUX)
.create();
String json = gson.toJson(map);
assertEquals(EXPECTED.replace("<EOL>", "\n"), json);
}

@Test
public void testNewlineCr() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setNewlineStyle(NewlineStyle.OLD_MACOS)
.create();
String json = gson.toJson(map);
assertEquals(EXPECTED.replace("<EOL>", "\r"), json);
}

@Test
public void testNewlineOs() {
Gson gson = new GsonBuilder()
.setPrettyPrinting()
.setNewlineStyle(NewlineStyle.CURRENT_OS)
.create();
String json = gson.toJson(map);
assertEquals(EXPECTED.replace("<EOL>", System.lineSeparator()), json);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,12 @@ public void testJsonValue() throws IOException {
* methods of {@code JsonWriter} must be overridden.
*/
public void testOverrides() {
List<String> ignoredMethods = Arrays.asList("setLenient(boolean)", "isLenient()", "setIndent(java.lang.String)",
"setHtmlSafe(boolean)", "isHtmlSafe()", "setSerializeNulls(boolean)", "getSerializeNulls()");
List<String> ignoredMethods = Arrays.asList(
"setLenient(boolean)", "isLenient()",
"setIndent(java.lang.String)",
"setHtmlSafe(boolean)", "isHtmlSafe()",
"setNewline(java.lang.String)", "getNewline()",
"setSerializeNulls(boolean)", "getSerializeNulls()");
MoreAsserts.assertOverridesMethods(JsonWriter.class, JsonTreeWriter.class, ignoredMethods);
}
}