Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
P
protobuf
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Packages
Packages
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
submodule
protobuf
Commits
115e6c73
Commit
115e6c73
authored
Aug 03, 2015
by
Jon Skeet
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #671 from jskeet/json-time
JSON formatting for Timestamp and Duration
parents
80f89b4e
801b169b
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
174 additions
and
11 deletions
+174
-11
unittest_issues.proto
csharp/protos/extest/unittest_issues.proto
+4
-1
JsonFormatterTest.cs
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
+61
-0
JsonFormatter.cs
csharp/src/Google.Protobuf/JsonFormatter.cs
+108
-9
TimestampPartial.cs
...rp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs
+1
-1
No files found.
csharp/protos/extest/unittest_issues.proto
View file @
115e6c73
...
@@ -97,7 +97,10 @@ message TestJsonFieldOrdering {
...
@@ -97,7 +97,10 @@ message TestJsonFieldOrdering {
// ordering.
// ordering.
// TestFieldOrderings in unittest_proto3.proto is similar,
// TestFieldOrderings in unittest_proto3.proto is similar,
// but doesn't include oneofs.
// but doesn't include oneofs.
// TODO: Consider adding
// TODO: Consider adding oneofs to TestFieldOrderings, although
// that will require fixing other tests in multiple platforms.
// Alternatively, consider just adding this to
// unittest_proto3.proto if multiple platforms want it.
int32
plain_int32
=
4
;
int32
plain_int32
=
4
;
...
...
csharp/src/Google.Protobuf.Test/JsonFormatterTest.cs
View file @
115e6c73
...
@@ -34,6 +34,7 @@ using System;
...
@@ -34,6 +34,7 @@ using System;
using
Google.Protobuf.TestProtos
;
using
Google.Protobuf.TestProtos
;
using
NUnit.Framework
;
using
NUnit.Framework
;
using
UnitTest.Issues.TestProtos
;
using
UnitTest.Issues.TestProtos
;
using
Google.Protobuf.WellKnownTypes
;
namespace
Google.Protobuf
namespace
Google.Protobuf
{
{
...
@@ -310,6 +311,66 @@ namespace Google.Protobuf
...
@@ -310,6 +311,66 @@ namespace Google.Protobuf
AssertJson
(
"{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }"
,
formatter
.
Format
(
message
));
AssertJson
(
"{ 'plainString': 'plain', 'o1String': '', 'plainInt32': 10, 'o2Int32': 0 }"
,
formatter
.
Format
(
message
));
}
}
[
Test
]
public
void
TimestampStandalone
()
{
Assert
.
AreEqual
(
"1970-01-01T00:00:00Z"
,
new
Timestamp
().
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.100Z"
,
new
Timestamp
{
Nanos
=
100000000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.120Z"
,
new
Timestamp
{
Nanos
=
120000000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123Z"
,
new
Timestamp
{
Nanos
=
123000000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123400Z"
,
new
Timestamp
{
Nanos
=
123400000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123450Z"
,
new
Timestamp
{
Nanos
=
123450000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123456Z"
,
new
Timestamp
{
Nanos
=
123456000
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123456700Z"
,
new
Timestamp
{
Nanos
=
123456700
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123456780Z"
,
new
Timestamp
{
Nanos
=
123456780
}.
ToString
());
Assert
.
AreEqual
(
"1970-01-01T00:00:00.123456789Z"
,
new
Timestamp
{
Nanos
=
123456789
}.
ToString
());
// One before and one after the Unix epoch
Assert
.
AreEqual
(
"1673-06-19T12:34:56Z"
,
new
DateTime
(
1673
,
6
,
19
,
12
,
34
,
56
,
DateTimeKind
.
Utc
).
ToTimestamp
().
ToString
());
Assert
.
AreEqual
(
"2015-07-31T10:29:34Z"
,
new
DateTime
(
2015
,
7
,
31
,
10
,
29
,
34
,
DateTimeKind
.
Utc
).
ToTimestamp
().
ToString
());
}
[
Test
]
public
void
TimestampField
()
{
var
message
=
new
TestWellKnownTypes
{
TimestampField
=
new
Timestamp
()
};
AssertJson
(
"{ 'timestampField': '1970-01-01T00:00:00Z' }"
,
JsonFormatter
.
Default
.
Format
(
message
));
}
[
Test
]
[
TestCase
(
0
,
0
,
"0s"
)]
[
TestCase
(
1
,
0
,
"1s"
)]
[
TestCase
(-
1
,
0
,
"-1s"
)]
[
TestCase
(
0
,
100000000
,
"0.100s"
)]
[
TestCase
(
0
,
120000000
,
"0.120s"
)]
[
TestCase
(
0
,
123000000
,
"0.123s"
)]
[
TestCase
(
0
,
123400000
,
"0.123400s"
)]
[
TestCase
(
0
,
123450000
,
"0.123450s"
)]
[
TestCase
(
0
,
123456000
,
"0.123456s"
)]
[
TestCase
(
0
,
123456700
,
"0.123456700s"
)]
[
TestCase
(
0
,
123456780
,
"0.123456780s"
)]
[
TestCase
(
0
,
123456789
,
"0.123456789s"
)]
[
TestCase
(
0
,
-
100000000
,
"-0.100s"
)]
[
TestCase
(
1
,
100000000
,
"1.100s"
)]
[
TestCase
(-
1
,
-
100000000
,
"-1.100s"
)]
// Non-normalized examples
[
TestCase
(
1
,
2123456789
,
"3.123456789s"
)]
[
TestCase
(
1
,
-
100000000
,
"0.900s"
)]
public
void
DurationStandalone
(
long
seconds
,
int
nanoseconds
,
string
expected
)
{
Assert
.
AreEqual
(
expected
,
new
Duration
{
Seconds
=
seconds
,
Nanos
=
nanoseconds
}.
ToString
());
}
[
Test
]
public
void
DurationField
()
{
var
message
=
new
TestWellKnownTypes
{
DurationField
=
new
Duration
()
};
AssertJson
(
"{ 'durationField': '0s' }"
,
JsonFormatter
.
Default
.
Format
(
message
));
}
/// <summary>
/// <summary>
/// Checks that the actual JSON is the same as the expected JSON - but after replacing
/// Checks that the actual JSON is the same as the expected JSON - but after replacing
/// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
/// all apostrophes in the expected JSON with double quotes. This basically makes the tests easier
...
...
csharp/src/Google.Protobuf/JsonFormatter.cs
View file @
115e6c73
...
@@ -122,10 +122,14 @@ namespace Google.Protobuf
...
@@ -122,10 +122,14 @@ namespace Google.Protobuf
{
{
Preconditions
.
CheckNotNull
(
message
,
"message"
);
Preconditions
.
CheckNotNull
(
message
,
"message"
);
StringBuilder
builder
=
new
StringBuilder
();
StringBuilder
builder
=
new
StringBuilder
();
// TODO(jonskeet): Handle well-known types here.
if
(
message
.
Descriptor
.
IsWellKnownType
)
// Our reflection support needs improving so that we can get at the descriptor
{
// to find out whether *this* message is a well-known type.
WriteWellKnownTypeValue
(
builder
,
message
.
Descriptor
,
message
,
false
);
WriteMessage
(
builder
,
message
);
}
else
{
WriteMessage
(
builder
,
message
);
}
return
builder
.
ToString
();
return
builder
.
ToString
();
}
}
...
@@ -356,7 +360,7 @@ namespace Google.Protobuf
...
@@ -356,7 +360,7 @@ namespace Google.Protobuf
case
FieldType
.
Group
:
// Never expect to get this, but...
case
FieldType
.
Group
:
// Never expect to get this, but...
if
(
descriptor
.
MessageType
.
IsWellKnownType
)
if
(
descriptor
.
MessageType
.
IsWellKnownType
)
{
{
WriteWellKnownTypeValue
(
builder
,
descriptor
,
val
ue
);
WriteWellKnownTypeValue
(
builder
,
descriptor
.
MessageType
,
value
,
tr
ue
);
}
}
else
else
{
{
...
@@ -370,20 +374,115 @@ namespace Google.Protobuf
...
@@ -370,20 +374,115 @@ namespace Google.Protobuf
/// <summary>
/// <summary>
/// Central interception point for well-known type formatting. Any well-known types which
/// Central interception point for well-known type formatting. Any well-known types which
/// don't need special handling can fall back to WriteMessage.
/// don't need special handling can fall back to WriteMessage. We avoid assuming that the
/// values are using the embedded well-known types, in order to allow for dynamic messages
/// in the future.
/// </summary>
/// </summary>
private
void
WriteWellKnownTypeValue
(
StringBuilder
builder
,
FieldDescriptor
descriptor
,
object
value
)
private
void
WriteWellKnownTypeValue
(
StringBuilder
builder
,
MessageDescriptor
descriptor
,
object
value
,
bool
inField
)
{
{
// For wrapper types, the value will be the (possibly boxed) "native" value,
// For wrapper types, the value will be the (possibly boxed) "native" value,
// so we can write it as if we were unconditionally writing the Value field for the wrapper type.
// so we can write it as if we were unconditionally writing the Value field for the wrapper type.
if
(
descriptor
.
MessageType
.
File
==
Int32Value
.
Descriptor
.
File
&&
value
!=
null
)
if
(
descriptor
.
File
==
Int32Value
.
Descriptor
.
File
&&
value
!=
null
)
{
WriteSingleValue
(
builder
,
descriptor
.
FindFieldByNumber
(
1
),
value
);
return
;
}
if
(
descriptor
.
FullName
==
Timestamp
.
Descriptor
.
FullName
&&
value
!=
null
)
{
MaybeWrapInString
(
builder
,
value
,
WriteTimestamp
,
inField
);
return
;
}
if
(
descriptor
.
FullName
==
Duration
.
Descriptor
.
FullName
&&
value
!=
null
)
{
{
WriteSingleValue
(
builder
,
descriptor
.
MessageType
.
FindFieldByNumber
(
1
),
value
);
MaybeWrapInString
(
builder
,
value
,
WriteDuration
,
inField
);
return
;
return
;
}
}
WriteMessage
(
builder
,
(
IMessage
)
value
);
WriteMessage
(
builder
,
(
IMessage
)
value
);
}
}
/// <summary>
/// Some well-known types end up as string values... so they need wrapping in quotes, but only
/// when they're being used as fields within another message.
/// </summary>
private
void
MaybeWrapInString
(
StringBuilder
builder
,
object
value
,
Action
<
StringBuilder
,
IMessage
>
action
,
bool
inField
)
{
if
(
inField
)
{
builder
.
Append
(
'"'
);
action
(
builder
,
(
IMessage
)
value
);
builder
.
Append
(
'"'
);
}
else
{
action
(
builder
,
(
IMessage
)
value
);
}
}
private
void
WriteTimestamp
(
StringBuilder
builder
,
IMessage
value
)
{
// TODO: In the common case where this *is* using the built-in Timestamp type, we could
// avoid all the reflection at this point, by casting to Timestamp. In the interests of
// avoiding subtle bugs, don't do that until we've implemented DynamicMessage so that we can prove
// it still works in that case.
int
nanos
=
(
int
)
value
.
Descriptor
.
Fields
[
Timestamp
.
NanosFieldNumber
].
Accessor
.
GetValue
(
value
);
long
seconds
=
(
long
)
value
.
Descriptor
.
Fields
[
Timestamp
.
SecondsFieldNumber
].
Accessor
.
GetValue
(
value
);
// Even if the original message isn't using the built-in classes, we can still build one... and then
// rely on it being normalized.
Timestamp
normalized
=
Timestamp
.
Normalize
(
seconds
,
nanos
);
// Use .NET's formatting for the value down to the second, including an opening double quote (as it's a string value)
DateTime
dateTime
=
normalized
.
ToDateTime
();
builder
.
Append
(
dateTime
.
ToString
(
"yyyy'-'MM'-'dd'T'HH:mm:ss"
,
CultureInfo
.
InvariantCulture
));
AppendNanoseconds
(
builder
,
Math
.
Abs
(
normalized
.
Nanos
));
builder
.
Append
(
'Z'
);
}
private
void
WriteDuration
(
StringBuilder
builder
,
IMessage
value
)
{
// TODO: Same as for WriteTimestamp
int
nanos
=
(
int
)
value
.
Descriptor
.
Fields
[
Duration
.
NanosFieldNumber
].
Accessor
.
GetValue
(
value
);
long
seconds
=
(
long
)
value
.
Descriptor
.
Fields
[
Duration
.
SecondsFieldNumber
].
Accessor
.
GetValue
(
value
);
// Even if the original message isn't using the built-in classes, we can still build one... and then
// rely on it being normalized.
Duration
normalized
=
Duration
.
Normalize
(
seconds
,
nanos
);
// The seconds part will normally provide the minus sign if we need it, but not if it's 0...
if
(
normalized
.
Seconds
==
0
&&
normalized
.
Nanos
<
0
)
{
builder
.
Append
(
'-'
);
}
builder
.
Append
(
normalized
.
Seconds
.
ToString
(
"d"
,
CultureInfo
.
InvariantCulture
));
AppendNanoseconds
(
builder
,
Math
.
Abs
(
normalized
.
Nanos
));
builder
.
Append
(
's'
);
}
/// <summary>
/// Appends a number of nanoseconds to a StringBuilder. Either 0 digits are added (in which
/// case no "." is appended), or 3 6 or 9 digits.
/// </summary>
private
static
void
AppendNanoseconds
(
StringBuilder
builder
,
int
nanos
)
{
if
(
nanos
!=
0
)
{
builder
.
Append
(
'.'
);
// Output to 3, 6 or 9 digits.
if
(
nanos
%
1000000
==
0
)
{
builder
.
Append
((
nanos
/
1000000
).
ToString
(
"d"
,
CultureInfo
.
InvariantCulture
));
}
else
if
(
nanos
%
1000
==
0
)
{
builder
.
Append
((
nanos
/
1000
).
ToString
(
"d"
,
CultureInfo
.
InvariantCulture
));
}
else
{
builder
.
Append
(
nanos
.
ToString
(
"d"
,
CultureInfo
.
InvariantCulture
));
}
}
}
private
void
WriteList
(
StringBuilder
builder
,
IFieldAccessor
accessor
,
IList
list
)
private
void
WriteList
(
StringBuilder
builder
,
IFieldAccessor
accessor
,
IList
list
)
{
{
builder
.
Append
(
"[ "
);
builder
.
Append
(
"[ "
);
...
...
csharp/src/Google.Protobuf/WellKnownTypes/TimestampPartial.cs
View file @
115e6c73
...
@@ -147,7 +147,7 @@ namespace Google.Protobuf.WellKnownTypes
...
@@ -147,7 +147,7 @@ namespace Google.Protobuf.WellKnownTypes
return
FromDateTime
(
dateTimeOffset
.
UtcDateTime
);
return
FromDateTime
(
dateTimeOffset
.
UtcDateTime
);
}
}
private
static
Timestamp
Normalize
(
long
seconds
,
int
nanoseconds
)
internal
static
Timestamp
Normalize
(
long
seconds
,
int
nanoseconds
)
{
{
int
extraSeconds
=
nanoseconds
/
Duration
.
NanosecondsPerSecond
;
int
extraSeconds
=
nanoseconds
/
Duration
.
NanosecondsPerSecond
;
seconds
+=
extraSeconds
;
seconds
+=
extraSeconds
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment